Шаблон стратегии без заявлений "переключателя"?
Я читал шаблон стратегии и у меня есть вопрос. Я реализовал очень простое консольное приложение ниже, чтобы объяснить, что я спрашиваю.
Я читал, что наличие операторов "switch" - это красный флаг при реализации шаблона стратегии. Тем не менее, я не могу избежать использования оператора switch в этом примере. Я что-то пропустил? Мне удалось убрать логику из карандаша, но теперь в моем Main есть оператор switch. Я понимаю, что мог бы легко создать новый класс TriangleDrawer, и мне не пришлось бы открывать класс Pencil, что хорошо. Однако мне нужно открыть Main, чтобы он знал, какой тип IDrawer передать на Карандаш. Это только то, что нужно сделать, если я полагаюсь на пользователя для ввода? Если есть способ сделать это без оператора switch, я бы хотел это увидеть!
class Program
{
public class Pencil
{
private IDraw drawer;
public Pencil(IDraw iDrawer)
{
drawer = iDrawer;
}
public void Draw()
{
drawer.Draw();
}
}
public interface IDraw
{
void Draw();
}
public class CircleDrawer : IDraw
{
public void Draw()
{
Console.Write("()\n");
}
}
public class SquareDrawer : IDraw
{
public void Draw()
{
Console.WriteLine("[]\n");
}
}
static void Main(string[] args)
{
Console.WriteLine("What would you like to draw? 1:Circle or 2:Sqaure");
int input;
if (int.TryParse(Console.ReadLine(), out input))
{
Pencil pencil = null;
switch (input)
{
case 1:
pencil = new Pencil(new CircleDrawer());
break;
case 2:
pencil = new Pencil(new SquareDrawer());
break;
default:
return;
}
pencil.Draw();
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
}
}
Реализованное решение, показанное ниже (спасибо всем, кто откликнулся!) Это решение привело меня к тому, что единственное, что мне нужно сделать, чтобы использовать новый объект IDraw, - это создать его.
public class Pencil
{
private IDraw drawer;
public Pencil(IDraw iDrawer)
{
drawer = iDrawer;
}
public void Draw()
{
drawer.Draw();
}
}
public interface IDraw
{
int ID { get; }
void Draw();
}
public class CircleDrawer : IDraw
{
public void Draw()
{
Console.Write("()\n");
}
public int ID
{
get { return 1; }
}
}
public class SquareDrawer : IDraw
{
public void Draw()
{
Console.WriteLine("[]\n");
}
public int ID
{
get { return 2; }
}
}
public static class DrawingBuilderFactor
{
private static List<IDraw> drawers = new List<IDraw>();
public static IDraw GetDrawer(int drawerId)
{
if (drawers.Count == 0)
{
drawers = Assembly.GetExecutingAssembly()
.GetTypes()
.Where(type => typeof(IDraw).IsAssignableFrom(type) && type.IsClass)
.Select(type => Activator.CreateInstance(type))
.Cast<IDraw>()
.ToList();
}
return drawers.Where(drawer => drawer.ID == drawerId).FirstOrDefault();
}
}
static void Main(string[] args)
{
int input = 1;
while (input != 0)
{
Console.WriteLine("What would you like to draw? 1:Circle or 2:Sqaure");
if (int.TryParse(Console.ReadLine(), out input))
{
Pencil pencil = null;
IDraw drawer = DrawingBuilderFactor.GetDrawer(input);
pencil = new Pencil(drawer);
pencil.Draw();
}
}
}
5 ответов
Стратегия не является волшебным решением против переключения. Что он делает, так это дает модульную структуру вашего кода, так что вместо большого коммутатора и бизнес-логики все смешалось в кошмаре обслуживания
- ваша бизнес-логика изолирована и открыта для расширения
- у вас есть варианты создания конкретных классов (см., например, шаблоны фабрики)
- код вашей инфраструктуры (ваш основной) может быть очень чистым, без
Например, если вы взяли переключатель в своем методе main и создали класс, который принял аргумент командной строки и возвратил экземпляр IDraw (т.е. он инкапсулирует этот переключатель), ваш main снова чист, и ваш переключатель находится в классе, единственное предназначение которого это реализовать этот выбор.
Ниже приведено более продуманное решение вашей проблемы исключительно во избежание if
/switch
заявления.
CircleFactory: IDrawFactory
{
string Key { get; }
IDraw Create();
}
TriangleFactory: IDrawFactory
{
string Key { get; }
IDraw Create();
}
DrawFactory
{
List<IDrawFactory> Factories { get; }
IDraw Create(string key)
{
var factory = Factories.FirstOrDefault(f=>f.Key.Equals(key));
if (factory == null)
throw new ArgumentException();
return factory.Create();
}
}
void Main()
{
DrawFactory factory = new DrawFactory();
factory.Create("circle");
}
Я не думаю, что ваш переход здесь в вашем демонстрационном приложении на самом деле является частью самого шаблона стратегии, он просто используется для реализации двух разных стратегий, которые вы определили.
Предупреждение "переключатели - красный флаг" означает наличие переключателей внутри стратегии; например, если вы определили стратегию "GenericDrawer" и попросили ее определить, хочет ли пользователь использовать SquareDrawer или CircleDrawer для внутреннего использования переключателя на значение параметра, вы не получите преимущества шаблона стратегии.
Вы также можете избавиться от if
с помощью словаря
Dictionary<string, Func<IDraw> factory> drawFactories = new Dictionary<string, Func<IDraw> factory>() { {"circle", f=> new CircleDraw()}, {"square", f=> new SquareDraw()}}();
Func<IDraw> factory;
drawFactories.TryGetValue("circle", out factory);
IDraw draw = factory();
Немного поздно, но для тех, кто по-прежнему заинтересован в полном удалении условного выражения.
class Program
{
Lazy<Dictionary<Enum, Func<IStrategy>>> dictionary = new Lazy<Dictionary<Enum, Func<IStrategy>>>(
() =>
new Dictionary<Enum, Func<IStrategy>>()
{
{ Enum.StrategyA, () => { return new StrategyA(); } },
{ Enum.StrategyB, () => { return new StrategyB(); } }
}
);
IStrategy _strategy;
IStrategy Client(Enum enu)
{
Func<IStrategy> _func
if (dictionary.Value.TryGetValue(enu, out _func ))
{
_strategy = _func.Invoke();
}
return _strategy ?? default(IStrategy);
}
static void Main(string[] args)
{
Program p = new Program();
var x = p.Client(Enum.StrategyB);
x.Create();
}
}
public enum Enum : int
{
StrategyA = 1,
StrategyB = 2
}
public interface IStrategy
{
void Create();
}
public class StrategyA : IStrategy
{
public void Create()
{
Console.WriteLine("A");
}
}
public class StrategyB : IStrategy
{
public void Create()
{
Console.WriteLine("B");
}
}