Есть следующий код:
public abstract class PointBase
{
}
public class ProductPoint : PointBase
{
}
public class EmptyPoint : PointBase
{
}
...
public void SomeMethod(IEnumerable<PointBase> points)
{
points.ForEach(DoSome);
}
public void DoSome(PointBase point)
{
if (point is ProductPoint)
{
DoSomeForProductPoint((ProductPoint)point);
return;
}
if (point is EmptyPoint)
{
DoSomeForEmptyPoint((EmptyPoint)point);
return;
}
throw new Exception("An unknown implementation.");
}
Мест с таким type checking'ом несколько.
Что хочется?
Чтобы при добавлении нового наследника от PointBase среда разработки сама мне говорила какие места с type checking'ом мне нужно дополнить чтобы они работали с новым наследником.
Чтобы нельзя было скомпилировать код (ну или в крайнем случае прогнать удачно тесты) без дополнения всех мест с type checking'ом для нового наследника.
Решения в лоб:
Какой-то волшебный метод в базовом классе с примерно следующим содержанием:
public static void DoSomeForDerived(
PointBase p,
Action<ProductPoint> a1,
Action<EmptyPoint> a2)
{
if (point is ProductPoint)
{
a1((ProductPoint)point);
return;
}
if (point is EmptyPoint)
{
a2((EmptyPoint)point);
return;
}
throw new Exception("An unknown implementation.");
}
И собственно вызывать этот метод каждый раз когда мне нужен какой-то type checking прокидывая в него экшены которые нужно выполнять для каждого типа наследников. С таким решением при добавлении нового наследника, например какого-нибудь "CodePoint : PointBase", и добавлением нового экшена в сигнатуру метода компилятор при следующем билде скажет мне что все прежние использования этого метода содержат неполный набор аргуметов. И я, разработчик, буду вынужден тем самым заимплементить логику работы с новым наследником во всех местах.
Ни один из вариантов мне не нравится. Буду рад увидеть любые предложения.
Помимо того, у вас в коде присутствует проверка типа (а это само по-себе проблема и вы её описываете) потенциально метод DoSome нарушает SRP. Я бы отделил операции DoSomeForEmptyPoint
и DoSomeForProductPoint
в отдельное место и сделал бы вызовы непосредственно из классов ProductPoint
и EmptyPoint
воспользовавшись паттерном визитер, например:
public interface IPointVisitor
{
void Visit(ProductPoint productPoint);
void Visit(EmptyPoint emptyPoint);
}
class PointVisitor : IPointVisitor
{
public void Visit(ProductPoint productPoint)
{
// DoSomeForProductPoint
}
public void Visit(EmptyPoint emptyPoint)
{
// DoSomeForEmptyPoint
}
}
public abstract class PointBase
{
public abstract void Accept(IPointVisitor visitor);
}
public class ProductPoint : PointBase
{
public override void Accept(IPointVisitor visitor)
{
visitor.Visit(this);
}
}
public class EmptyPoint : PointBase
{
public override void Accept(IPointVisitor visitor)
{
visitor.Visit(this);
}
}
public class Program
{
public static void Main()
{
IPointVisitor visitor = new PointVisitor();
ProductPoint point = new ProductPoint();
point.Accept(visitor);
}
}
Из минусов подхода, при появлении нового класса в иерархии вам придется переделывать интерфейс визитера. С другой стороны, у вас появится простой полиморфный способ расширения операций над этой иерархией.
По моему, вы пытаетесь заново изобрести полиморфизм.
public abstract class PointBase
{
public virtual void DoSome()
{
throw new NotImplementedException();
//Если метод не реализован в наследнике будет исключение
}
}
public class ProductPoint : PointBase
{
public override void DoSome()
{
//что то делаем...
}
}
public class EmptyPoint : PointBase
{
public override void DoSome()
{
//что то делаем...
}
}
public void SomeMethod(IEnumerable<PointBase> points)
{
foreach (point in points)
{
point.DoSome();
}
}
З.Ы. Если родительский класс определить как:
public abstract class PointBase
{
public abstract void DoSome();
}
то, без переопределения данного метода в наследнике, вы получите ошибку, еще на этапе компиляции. Какой из этих методов будет удобнее, зависит собственно от вас, и от структуры вашего проекта.
Кофе для программистов: как напиток влияет на продуктивность кодеров?
Рекламные вывески: как привлечь внимание и увеличить продажи
Стратегії та тренди в SMM - Технології, що формують майбутнє сьогодні
Выделенный сервер, что это, для чего нужен и какие характеристики важны?
Современные решения для бизнеса: как облачные и виртуальные технологии меняют рынок
Вызов метода должен отправить такой ответ:
Задача: сделать крутящийся кружок, пока приложение будет делать свои делаДля этой задачи пробовал использовать gif, но есть разные проблемы,...