Как выполнить рефакторинг вызова методов, который выглядит так же? - PullRequest
3 голосов
/ 28 декабря 2011

У меня разные классы фруктов, все они реализуют один и тот же интерфейс

public interface IApple : IFruit{ }  
public interface IBanana : IFruit{ }  
public interface ICarrot: IFruit{ }  

У каждого из них есть свой ящик:

public class AppleDrawer
{
    public void Draw(IApple apple, Graphics graphics){}
}

public class BananaDrawer
{
    public void Draw(IBanana banana, Graphics graphics){}
}

Если я хочу составить список фруктов, я делаю следующее

public void DrawFruits(List<IFruit> fruits, Graphics graphics)
{
    foreach(var fruit in fruits)
    {
        if(fruit is IBanana)
        {
            var banana = (IBanana)fruit;
            var drawer = new BananaDrawer();
            drawer.Draw(banana, graphics);
        }
        else if(fruit is IApple)
        {
            var apple = (IApple)fruit;
            var drawer = new AppleDrawer();
            drawer.Draw(banana, graphics);
        }
        etc...

}

Я чувствую себя очень грязно, когда читаю мой код.
Моя проблема заключается в многократном утверждении if..else, потому что у меня 12 разных плодов, и я должен много делать это утверждение в моем текущем проекте.

Есть ли способ рефакторинга моего метода DrawFruits?
Я думаю о каком-то заводском паттерне, но не понимаю, как это сделать. Должен ли мой фруктовый класс взять Ящик в собственность? Или, может быть, я могу вызвать метод Drawer Factory?

Это образец, который я часто нахожу в моем текущем проекте, и я не нахожу решение, которое меня устраивает.

Ответы [ 4 ]

5 голосов
/ 28 декабря 2011

Одним из способов является GetDrawer на вашем IFruit

public interface IFruit 
{
    BaseDrawer GetDrawer();
}

и BaseDrawer интерфейс

public interface BaseDrawer
{
    void Draw(IFruit fruit, Graphics graphics);
}.

public class AppleDrawer : BaseDrawer
{
    public void Draw(IFruit apple, Graphics graphics) { }
}

public class BananaDrawer : BaseDrawer
{
    public void Draw(IFruit banana, Graphics graphics) { }
}

Теперь твоя ничья Фрукты просто

   public void DrawFruits(List<IFruit> fruits, Graphics graphics)
    {
        foreach (var fruit in fruits)
        {
            var drawer = fruit.GetDrawer();
            drawer.Draw(fruit, graphics);
        }
    }

Иногда вам нужны Drawer, Plotter и Printer, чтобы ваш IFruit мог стать слишком тяжелым, как показано ниже

public interface IFruit 
{
    BaseDrawer GetDrawer();
    BasePrinter GetPrinter();
    BasePlotter GetPlotter();
}

Шаблон посетителя - хорошее решение для этого . В основном у вас будет

 public interface iFruit
   {
      void Accept(FruitVisitor visitor);
   } 

только один класс для всех возможных посещений рисунка

public class DrawVisitor : FruitVisitor 
   {
      public override void Visit(Apple apple)
      {
         //draw the apple
      }

      public override void Visit(Banana banana)
      { 
         // draw the banana
      }
   }

Здесь у вас есть только один DrawVisitor вместо AppleDrawer, BananaDrawer и т. Д., И весь ваш код прорисовки находится в одном месте. Вам может понадобиться PlotterVisitor, PrinterVisiter и т. Д.

4 голосов
/ 28 декабря 2011

Возможно, вы могли бы создать абстрактный класс Fruit с помощью метода Draw и абстрактный класс FruitDrawer соответственно.

Например:


public abstract class Fruit {
  ...
}

public abstract class FruitDrawer {
  public void Draw(Fruit f, Graphics g)
  {
    ...
  }
}
2 голосов
/ 28 декабря 2011

Вы в основном изрыгнули канонический пример нарушения открытого-закрытого принципа , но с фруктами вместо форм.

Можно погрузиться во все ваши вопросы, но если вы изучите ТВЕРДЫЕ Принципы , вы узнаете намного больше.

По просьбам я углублюсь в ваш код.

Прежде всего, я бы вставил Draw в интерфейс IFruit, чтобы каждый фрукт отвечал за рисование, а не за класс контроллера, управляющий всем. Вы могли бы закончить с нарушениями Single Responsibility (S в SOLID), но это был бы другой рефакторинг. Здесь важно то, что фрукты извлекаются сами, а контроллер открыт для расширения (добавление большего количества классов фруктов), но закрыт для модификаций (поскольку они рисуют сами, контроллер никогда не меняется).

Затем получается простая петля, тянущая ваши фрукты.

foreach(var fruit in fruits)
    fruit.Draw(...);

Чтобы ответить на комментарий "как не [сломать] их" ...

Знание того, чем они являются, является первым шагом на пути к обучению и применению их. Самый простой способ избежать их нарушения (и это не совсем просто) - быть очень строгим в разработке, управляемой тестами. Многое из того, что вы привели в качестве примеров, было бы очень сложно осознанно сделать через TDD. Другими словами, если вам известны принципы SOLID и , выполняющие TDD, вы не получите код, который вы опубликовали.

1 голос
/ 28 декабря 2011

Я думаю, что лучше сделать так:

interface IFruit
{
   void Draw();
}

class Banana : IFruit
{
   void Draw()
   {
      BananaDrawer drawer = new BananaDrawer();
      drawer.Draw();
   }
}

и использовать это просто:

foreach(var fruit in fruits)
   fruit.Draw();

Фактически таким образом вы можете запустить дополнительную инициализацию для конкретного ящика всвязанные фрукты.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...