Если вы обнаружите, что используете переключатель / футляр и литье, велика вероятность, что вы делаете это неправильно. При правильно спроектированной объектной модели в этом нет необходимости.
Например,
abstract public class Super
{
public int Number { get; set; }
public abstract void Do();
}
public class Type1 : Super
{
public string Info { get; set; }
public override void Do()
{
Console.WriteLine($"Got type 1 with {this.Info}");
}
}
public class Type2 : Super
{
public string Prop { get; set; }
public override void Do()
{
Console.WriteLine($"Got type 2 with {this.Prop}");
}
}
Теперь вы можете просто сделать это в своем l oop:
public static void Main(string[] args)
{
var list = new List<Super>()
{
new Type1 { Number = 1, Info = "infomatin" },
new Type2 { Number = 2, Prop = "propty" }
};
foreach (var t in list)
{
t.Do();
}
}
Вышеизложенное соответствует Говори, не спрашивай , который является традиционной объектно-ориентированной философией.
Если вы беспокоитесь о разделении проблем (например, вы не Я хочу, чтобы ваши классы знали "Консоль"), тогда вы можете добавить внешнюю функциональность:
abstract public class Super
{
public int Number { get; set; }
public abstract void Do(Action<int> action);
}
public class Type1 : Super
{
public string Info { get; set; }
public override void Do(Action<int> action)
{
action(this.Info);
}
}
public class Type2 : Super
{
public string Prop { get; set; }
public override void Do(Action<int> action)
{
action(this.Prop);
}
}
public static void Main(string[] args)
{
var list = new List<Super>()
{
new Type1 { Number = 1, Info = "infomatin" },
new Type2 { Number = 2, Prop = "propty" }
};
foreach (var t in list)
{
t.Do( x => Console.WriteLine("The value that we're interested in is {0}", x));
}
}
Есть еще одна ситуация, которая может иметь место здесь (на основе ваших комментариев). Допустим, у вас есть «чистые» объекты DTO, у которых нет методов, и вы не хотите добавлять их по какой-либо причине, например, возможно, DTO генерируются кодом, и вы не можете их изменять. На самом деле это обычная ситуация (мне тоже нравятся бездетные DTO).
Чтобы сделать ситуацию более реальной, давайте воспользуемся более значимыми примерами. Предположим, у вас есть множество объектов, которые могут содержать имя конечного пользователя, но с различными идентификаторами:
abstract public class Super
{
}
public class Type1 : Super
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Type2 : Super
{
public string FullName { get; set; }
}
Здесь возникает соблазн написать такой случай переключателя:
foreach (var t in list)
{
switch (t)
{
case Type1 type1 : Console.WriteLine("Name is {0} {1}", type1.FirstName, type1.LastName);
case Type2 type2: Console.WriteLine("Name is {0}", type2.FullName);
default:
throw new InvalidOperationException();
}
}
Проблема здесь в том, что время выполнения throw
будет происходить каждый раз, когда кто-то добавляет другой тип объекта, но не забывает обновить ваш оператор switch
. Это может не быть проблемой, но это также может быть огромной проблемой, например, если ваши DTO хранятся в отдельной библиотеке от вашего процессора Do
, и вам не нужно обновлять оба одновременно (что может быть проблема развертывания в определенных архитектурах).
Что здесь отсутствует, так это бизнес-концепция «Имя» агности c того, откуда оно взялось. Где-то какой-то код должен преобразовать эти различные объекты во что-то, что имеет имя, и желательно, чтобы logi c был где-то инкапсулирован.
Вот где я бы использовал класс адаптера.
class NameHolder
{
public string FullName { get; }
public NameHolder(Type1 type1)
{
this.FullName = type1.FirstName + " " + type1.LastName;
}
public NameHolder(Type2 type2)
{
this.FullName = type2.FullName;
}
}
С добавлением этой недостающей бизнес-концепции логи c становятся очень простыми:
public static void Main(string[] args)
{
var list = new List<NameHolder>()
{
new NameHolder(new Type1 { Number = 1, Info = "infomatin" }),
new NameHolder(new Type2 { Number = 2, Prop = "propty" })
};
foreach (var t in list)
{
Do(t.FullName);
}
}
Обратите внимание на отсутствие throw
. Преимущество этого подхода в том, что все типы разрешаются во время компиляции, поэтому, если вы забудете добавить logi c для сопоставления правильных полей, вы получите ошибку времени компиляции, которую вы можете обнаружить и немедленно исправить.