объединение декоратора и паттерна состояния в java - вопрос о ОО-дизайне - PullRequest
0 голосов
/ 13 ноября 2009

Я нахожусь в процессе решения проблемы, когда я думаю, что она лучше всего подходит для декоратора и шаблона состояния. Настройка высокого уровня - это что-то вроде сэндвичницы и диспенсера, где у меня есть определенное количество ингредиентов и несколько разных типов садовых сэндвичей, которые я могу приготовить. С каждым ингредиентом связана стоимость. Клиентом будет кто-то, кто будет использовать машину для выбора ингредиентов для приготовления конкретного сэндвича, и машина выдаст его.

На данный момент я создал ингредиенты и различные сэндвичи, используя шаблон декоратора:

public abstract class Sandwich {
    String description = "Unknown Sandwich";

    public String getDescription(){
        return description;
    }

    public double cost(){
        return 0.0;
    }
}

Каждый ингредиент моделируется так:

public abstract class Ingredient extends Sandwich {
    public abstract String getDescription();
}

И еще, конкретный ингредиент будет:

public class Cheese extends Ingredient {
    private Sandwich sandwich;

    public Cheese(Sandwich sandwich){
        this.sandwich = sandwich;
    }

    public String getDescription() {
        return sandwich.getDescription() + ", cheese";
    }

    public double cost() {
        return 0.25 + sandwich.cost();
    }
}

Конкретный тип сэндвича можно смоделировать так:

public class BLT extends Sandwich {
    public BLT(){
        description = "Bacon, Lettuce and Tomato";
    }
}

Таким образом, клиент может создать специальный бутерброд, подобный этому:

Sandwich order_a_blt = new Tomato(new Lettuce(new Bacon(new Bread(new BLT()))));

В качестве следующего шага я создам объект Dispenser, который будет действовать как автомат, который предварительно загружен определенным количеством ингредиентов (которые измеряются в общих единицах), и пользователь может нажать кнопку, чтобы выбрать один из предустановленных выборов:

Например

  • BLT: 1 единица томата, 1 единица салат, 1 единица бекона, 1 единица хлеба
  • SUB: 1 единица фрикадельки, 1 единица сыра, 1 единица итальянского соуса, 1 единица хлеба
  • и т.д ..

Мой дозатор поставляется с предустановленным количеством единиц на ингредиент

  • помидор: 10
  • салат: 10
  • бекон: 10
  • и т.д ..

И список кнопок для выбора пользователем определенного сэндвича:

  • 1-BLT
  • 2-SUB
  • 3-BBQ
  • .. и т.д. * * тысяча пятьдесят-один

Идея состоит в том, чтобы отслеживать внутреннюю емкость ингредиентов и быть в состоянии сказать пользователю, что, скажем, у нас недостаточно бекона, чтобы сделать еще один BLT

Теперь моя первоначальная мысль - создать объект Dispenser на основе шаблона проектирования состояний, но я столкнулся с проблемой, пытаясь объединить объекты класса Ingredient с каким-то хранилищем в классе Dispenser. Сначала я думал, что карта с именем / значением объединяет тип ингредиента / количество ингредиента. Но я не уверен, как объединить эти шаблоны вместе, чтобы я мог автоматически уменьшать после каждого использования.

Возможно, у вас есть общее представление о том, как действовать дальше и реализовывать такую ​​концепцию? Прежде всего, я на правильном пути с декоратором и шаблонами состояния? Будет ли более эффективный подход? Надеюсь, я четко объяснил проблему.

Спасибо за любое направление, я ценю любые мысли

Ответы [ 3 ]

3 голосов
/ 13 ноября 2009

Сэндвич с сыром имеет отношение «имеет-а», поэтому сэндвич никогда не должен быть родителем сыра.

Не уверен, что вы делаете в этой строке:

Sandwich order_a_blt = new Tomato(new Lettuce(new Bacon(new Bread(new BLT()))));

Если говорить логически, зачем создавать объект Tomato и передавать ему салат? Помидор, салат .... и т. Д. Должен расширять ингредиент.

Я бы сделал это так

class Sandwich{ public Sandwich(Ingredients ...ing){}}

Внутри каждого класса ингредиентов я бы поместил в Tomato статическую переменную, назвал бы ее tomatoCount, а затем инициализировал бы ее при создании диспенсера, каждый раз, когда создавался новый томат, уменьшал его. Если он достигнет нуля, то класс Tomato будет жаловаться

2 голосов
/ 13 ноября 2009
  1. Ингредиент не является бутербродом IS-A;
  2. Лучше выводить цены на ингредиенты для их гибкого изменения;
  3. Лучше генерировать бутерброд описание во время выполнения на основе его ингредиенты вместо жесткого на уровне класса;
  4. Ингредиенты не должны знать ничего о бутербродах;

Итак, я бы предложил следующее решение:

package com;

public enum Ingredient {

 CHEESE, TOMATO, LETTUCE, BACON, BREAD, MEATBALL, ITALIAN_SAUCE;

 private final String description;

 Ingredient() {
  description = toString().toLowerCase();
 }

 Ingredient(String description) {
  this.description = description;
 }

 public String getDescription() {
  return description;
 }
}


package com;

import static com.Ingredient.*;

import java.util.*;
import static java.util.Arrays.asList;

public enum SandwitchType {

 BLT(
   asList(TOMATO, LETTUCE, BACON, BREAD),
             1  ,    1,      1  ,   1
 ),
 SUB(
   asList(MEATBALL, CHEESE, ITALIAN_SAUCE, BREAD),
              1   ,    1  ,      1       ,   1
 );

 private final Map<Ingredient, Integer> ingredients = new EnumMap<Ingredient, Integer>(Ingredient.class);
 private final Map<Ingredient, Integer> ingredientsView = Collections.unmodifiableMap(ingredients);

 SandwitchType(Collection<Ingredient> ingredients, int ... unitsNumber) {
  int i = -1;
  for (Ingredient ingredient : ingredients) {
   if (++i >= unitsNumber.length) {
    throw new IllegalArgumentException(String.format("Can't create sandwitch %s. Reason: given ingedients "
      + "and their units number are inconsistent (%d ingredients, %d units number)", 
      this, ingredients.size(), unitsNumber.length));
   }
   this.ingredients.put(ingredient, unitsNumber[i]);
  }
 }

 public Map<Ingredient, Integer> getIngredients() {
  return ingredientsView;
 }

 public String getDescription() {
  StringBuilder result = new StringBuilder();
  for (Ingredient ingredient : ingredients.keySet()) {
   result.append(ingredient.getDescription()).append(", ");
  }

  if (result.length() > 1) {
   result.setLength(result.length() - 2);
  }
  return result.toString();
 }
}


package com;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

public class PriceList {

 private static final int PRECISION = 2;

 private final ConcurrentMap<Ingredient, Double> prices = new ConcurrentHashMap<Ingredient, Double>();

 public double getPrice(SandwitchType sandwitchType) {
  double result = 0;
  for (Map.Entry<Ingredient, Integer> entry : sandwitchType.getIngredients().entrySet()) {
   Double price = prices.get(entry.getKey());
   if (price == null) {
    throw new IllegalStateException(String.format("Can't calculate price for sandwitch type %s. Reason: "
      + "no price is defined for ingredient %s. Registered ingredient prices: %s",
      sandwitchType, entry.getKey(), prices));
   }
   result += price * entry.getValue();
  }
  return round(result);
 }

 public void setIngredientPrice(Ingredient ingredient, double price) {
  prices.put(ingredient, round(price));
 }

 private static double round(double d) {
  double multiplier = Math.pow(10, PRECISION);
  return Math.floor(d * multiplier + 0.5) / multiplier;
 }
}


package com;

import java.util.Map;
import java.util.EnumMap;

public class Dispenser {

 private final Map<Ingredient, Integer> availableIngredients = new EnumMap<Ingredient, Integer>(Ingredient.class);

 public String buySandwitch(SandwitchType sandwitchType) {
  StringBuilder result = new StringBuilder();
  synchronized (availableIngredients) {

   Map<Ingredient, Integer> buffer = new EnumMap<Ingredient, Integer>(availableIngredients);
   for (Map.Entry<Ingredient, Integer> entry : sandwitchType.getIngredients().entrySet()) {
    Integer currentNumber = buffer.get(entry.getKey());
    if (currentNumber == null || currentNumber < entry.getValue()) {
     result.append(String.format("not enough %s (required %d, available %d), ",
       entry.getKey().getDescription(), entry.getValue(), currentNumber == null ? 0 : currentNumber));
     continue;
    }
    buffer.put(entry.getKey(), currentNumber - entry.getValue());
   }

   if (result.length() <= 0) {
    availableIngredients.clear();
    availableIngredients.putAll(buffer);
    return "";
   }
  }
  if (result.length() > 1) {
   result.setLength(result.length() - 2);
  }
  return result.toString();
 }

 public void load(Ingredient ingredient, int unitsNumber) {
  synchronized (availableIngredients) {
   Integer currentNumber = availableIngredients.get(ingredient);
   if (currentNumber == null) {
    availableIngredients.put(ingredient, unitsNumber);
    return;
   }
   availableIngredients.put(ingredient, currentNumber + unitsNumber);
  }
 }
}


package com;

public class StartClass {
 public static void main(String[] args) {
  Dispenser dispenser = new Dispenser();
  for (Ingredient ingredient : Ingredient.values()) {
   dispenser.load(ingredient, 10);
  }
  PriceList priceList = loadPrices();
  while (true) {
   for (SandwitchType sandwitchType : SandwitchType.values()) {
    System.out.printf("About to buy %s sandwitch. Price is %f...",
      sandwitchType, priceList.getPrice(sandwitchType));
    String rejectReason = dispenser.buySandwitch(sandwitchType);
    if (!rejectReason.isEmpty()) {
     System.out.println(" Failed: " + rejectReason);
     return;
    }
    System.out.println(" Done");
   }
  }
 }

 private static PriceList loadPrices() {
  PriceList priceList = new PriceList();
  double i = 0.1;
  for (Ingredient ingredient : Ingredient.values()) {
   priceList.setIngredientPrice(ingredient, i);
   i *= 2;
  }
  return priceList;
 }
}
1 голос
/ 13 ноября 2009

Шаблон декоратора не подходит для вашей проблемы. Ингредиент не добавляет новое поведение к сэндвичу, не говоря уже о том, что связывание сэндвича и (сэндвич) ингредиента в отношениях is-a уже немного придумано. (Вложенные экземпляры выглядят только круто, пока вы не сделаете это динамически.)

Сэндвич имеет ингредиенты / начинки / приправы. Установите иерархию классов для ингредиентов и сложите их вместе с сэндвичем, используя составной шаблон.

public abstract class Ingredient {
    protected Ingredient(Object name) { ... }
    public String name() { ... }
    public abstract String description();
    public abstract double cost();
}

public Cheese extends Ingredient {
    public Cheese() { super("Cheese"); }
    public String description() { ... }
    public double cost() { return 0.25; }
|

public abstract class Sandwich {
   public abstract double cost();
   public Set<Ingredient> fillings() { ... }
   public boolean addFilling(Ingredient filling) { ... }
   public boolean removeFilling(Ingredient filling) { ... }
   public double totalFillingsCost();
   ...
}

public class SubmarineSandwich extends Sandwich {
   public SubmarineSandwich() { ... }
   public double cost() { return 2.50 + totalFillingsCost(); }   
}

public enum SandwichType { 
    Custom,
    Blt,
    Sub,
    ...
}

public class SandwichFactory  {
    public Sandwich createSandwich(SandwichType type) {
        switch (type) {
            case Custom:
                return new Sandwich() { public double cost() { return 1.25; } };
            case Blt:
                return new BaconLettuceTomatoSandwich();
            case Sub:
               return new SubmarineSandwich();
            ....
        }
    }
}

Кроме того, я не думаю, что модель состояния полезна для диспенсера, поскольку она связана с управлением ингредиентами или сэндвичами. Шаблон предписывает внутреннее использование объектов для изменения поведения класса. Но DIspenser не нуждается в полиморфном поведении в зависимости от состояния:

public class SandwichDispenser {
    ...
    public void prepareSandwich(SandwichType type) throws SupplyException { ... }
    public Sandwich finalizeSandwich() throws NotMakingASandwichException { ... }
    public boolean addFilling(Ingredient filling) throws SupplyException { ... } 
}

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

...