Как сделать ссылку поверхностной

#c#

#c#

Вопрос:

Мне интересно, как можно заставить одну переменную всегда указывать на то, на что указывает другая переменная.

Учитывая следующую программу :

 using System;

public class Program
{
    public static void Main()
    {
        Person p = new Person{Name = "Aelphaeis", Age = -1};
        p.introBehavior.Introduce();
    }

    class Person
    {
        public String Name { get;set; }
        public Int32 Age { get;set; }

        public IIntroductionBehavior introBehavior{get;set;}

        public Person(){
            Name = "over";
            Age = 9000;

            introBehavior = new IntroductionBehavior(Name, Age);
        }
    }

    class IntroductionBehavior : IIntroductionBehavior
    {
        String Name;
        Int32 Age;

        public IntroductionBehavior(string  name, int age){
            Age = age;
            Name = name;
        }

        public void Introduce(){
            Console.WriteLine("I'm "   Name   " and I am "   Age   " years old");
        }

    }

    interface IIntroductionBehavior
    {
        void Introduce();
    }   
}
  

Если вы запустите эту программу, вы получите

Я закончился, и мне 9000 лет

Желательным поведением было бы, чтобы имя и возраст внутри введения указывали на то, на что также указывает значение свойств внутри Person . Она должна выводить :

Я Эльфейс, и мне -1 год

Если это невозможно, какой тип редизайна я мог бы реализовать, чтобы гарантировать, что IntroductionBehavior всегда будет печатать значения Person, не информируя IntroductionBehavior о Person?

Редактировать: многие люди, кажется, сбиты с толку тем, почему я не хочу, чтобы IntroductionBehavior знал о человеке.

Введение на самом деле предназначено для выполнения операции над репозиторием. Имя переменных и возраст представляют репозиторий, которым будут манипулировать. Я пытаюсь свободно связать вещи, чтобы их было легко отлаживать.

Комментарии:

1. Почему вы просто не ссылаетесь на Person в своем IIntroductionBehavior?

2. @Ksven Хотя здесь это не кажется разумным, я бы хотел протестировать интерфейс с помощью тестовых примеров, и он должен быть способен функционировать независимо от пользователя.

3. Мне кажется, что это очень запутанный способ делать что-то. Почему бы вам просто не указать на Console.WriteLine материал с помощью delegate вместо создания отдельного класса, реализующего интерфейс?

4. Вы могли бы создавать новое поведение каждый раз, когда вам это нужно, или вы могли бы создать новый интерфейс для Person наследования. Я не уверен, что это было бы особенно полезно, хотя — почему вы не хотите ссылаться Person напрямую?

Ответ №1:

Дайте IntroductionBehavior ссылку на person:

 class IntroductionBehavior : IIntroductionBehavior
{
    private Person person;

    public IntroductionBehavior(Person person){
        this.person = person;
    }

    public void Introduce(){ 
        Console.WriteLine("I'm {0} and I am {1} years old", 
           person.Name, person.Age); // NOTE: use string format
    }
}

class Person
{
    public String Name { get; set; }
    public Int32 Age { get; set; }

    public IIntroductionBehavior introBehavior { get; set; }

    public Person(){
        Name = "over";
        Age = 9000;
        introBehavior = new IntroductionBehavior(this);
    }
}
  

Таким образом IntroductionBehavior вы получите значения имени и возраста или персоны в то время, когда вы попросите их представить.

Другим вариантом является создание IntroductionBehavior экземпляра в методе получения свойств вместо создания его в конструкторе:

   public IIntroductionBehavior introBehavior
  {
     get { return new IntroductionBehavior(Name, Age); }
  }
  

Таким образом IntroductionBehavior будут фиксироваться значения имени и возраста пользователя в момент его получения, а не во время создания пользователя. ПРИМЕЧАНИЕ: если вы обновите имя или возраст пользователя между его получением introBehavior и представлением, то увидите старые данные.

Конечно, использование IntroductionBehavior класса является спорным..

Комментарии:

1. Мне отчасти нравится это предложение. Я не думал о перегрузке конструктора, чтобы разрешить это. Я собираюсь посидеть над этим пару минут и подумать об этом.

2. @Aelphaeis в первом случае IntroductionBehavior действует как объект command — он инкапсулирует получателя (person) и может быть вызван позже, вызвав Introduce()

3. В конечном счете, я отказался от этого подхода; однако этот подход заставил меня достаточно подумать, чтобы прийти к выводу, и я думаю, что он наиболее полезен для этой цели, поэтому я пометил это как правильное.

Ответ №2:

Обычно то, что вы затем делаете в C #, — это передаете родительский объект его конструктору. В этом случае IntroductionBehavior сохранялся бы родительский файл Person .

Например:

 class IntroductionBehavior : ...
{
   public Person Parent {get; private set;}

   public IntroductionBehavior(Person parent)
   {
        Parent = parent;
   }

   ...
}
  

Затем вы можете использовать Parent.Age для получения возраста.

Ответ №3:

Три мысли:

1) Зачем нужен отдельный класс IntroductionBehavior , когда вы могли бы просто сделать:

 class Person : IIntroductionBehavior
{
    public String Name { get;set; }
    public Int32 Age { get;set; }

    public Person(){
        Name = "over";
        Age = 9000;
    }

    public void Introduce()
    {
        Console.WriteLine("I'm "   Name   " and I am "   Age   " years old");
    }
}
  

2) Вы могли бы реализовать свойства getters / setters для изменения поведения

 public String Name
{
    get { return introBehavior.Name; }
    set { introBehavior.Name = value; }
}

public Int32 Age
{
    get { return introBehavior.Age; }
    set { introBehavior.Age = value; }
}
  

3) Вы также могли бы инкапсулировать все поведение
Это может быть в сочетании с 2), поэтому вместо определения свойства introductionBehavior как настраиваемого, вы могли бы сделать это:

 private IntroductionBehavior introBehavior;

public Person()
{
    introBehavior = new IntroductionBehavior() { Name = "Test", Age = 123 };
}

public IIntroductionBehavior introductionBehavior
{
    get { return introBehavior; }
}

public String Name
{
    get { return introBehavior.Name; }
    set { introBehavior.Name = value; }
}

public Int32 Age
{
    get { return introBehavior.Age; }
    set { introBehavior.Age = value; }
}
  

Объедините 2) и 3), чтобы сохранить имя и возраст только в поведении вместо дублирования информации (2 свойства в Person , два свойства в introductionBehavior ).

Я нахожу довольно сомнительным, что introductionBehavior должна быть возможность настройки извне Person класса. Вот почему я думаю, что 3) — хороший подход. В комментариях к вопросу вы говорите, что все это касается автоматического тестирования интерфейса независимо от Person класса. На мой взгляд, комбинация 2) и 3) позволяет вам сделать именно это с дополнительным преимуществом, заключающимся в том, что части тестирования Person уже выполняются путем тестирования IntroductionBehavior .

Ответ №4:

Вы можете использовать следующий вспомогательный шаблон SharedValue<T> для создания ссылок на значения (т. е. добавить еще один уровень косвенности). Это позволяет вам совместно использовать значения. Вы должны добавить .Value к переменным, если хотите получить доступ к «реальному» значению, иначе вы просто получите «указатель». Это сравнимо с shared_ptr (или любыми другими конструкциями, похожими на указатель) в C .

 class SharedValue<T>
{
    T   Value; // stores a single value
}
  

Тогда ваш код выглядел бы так:

 class Person
{
    private readonly SharedValue<string> name = new SharedValue<string>();
    private readonly SharedValue<Int32> age = new SharedValue<Int32>();

    public String Name { get {return name.Value;} set {name.Value = value;} }
    public Int32 Age { get {return age.Value;} set {age.Value = value;} }

    public IIntroductionBehavior introBehavior{get;set;}

    public Person(){
        Name = "over";
        Age = 9000;

        introBehavior = new IntroductionBehavior(name, age); // note that we pass the SharedValues
    }
}

class IntroductionBehavior : IIntroductionBehavior
{
    SharedValue<String> Name;
    SharedValue<Int32> Age;

    public IntroductionBehavior(SharedValue<string>  name, SharedValue<int> age){
        Age = age;
        Name = name;
    }

    public void Introduce(){
        Console.WriteLine("I'm "   Name.Value   " and I am "   Age.Value   " years old");
    }

}    
  

Приведет ли это к хорошему общему дизайну — это совершенно другой вопрос!

Комментарии:

1. Это, безусловно, интересная мысль, 1 за мысль. Определенно, самое креативное решение