#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 за мысль. Определенно, самое креативное решение