GoF design pattern from the problem 3. Behavior

This is the behavior of the GoF design pattern series from the perspective of problems.

  1. Generation
  2. Structure
  3. Behavior

: question: I want to separate data and processing

before

public class FooAmusementPark {

  private FooZoo zoo;
  private FooAquarium aquarium;

  public void enjoy(FooFamily family) {
    zoo.enjoy();
  }

  public void enjoy(FooCouple couple) {
    aquarium.enjoy();
  }
}

If the number of customers visiting other than Family and Couple increases, we have to change the Amusement Park.

after :bulb: Visitor

public class FooAmusementPark {

  private FooZoo zoo;
  private FooAquarium aquarium;

  public void accept(FooVisitor visitor) {
    visitor.visit(this);
  }
}

public class FooFamily extends FooVisitor {

  @Override
  public void visit(FooAmusementPark park) {
    park.getZoo().enjoy();
  }
}

public class FooCouple extends FooVisitor {

  @Override
  public void visit(FooAmusementPark park) {
    park.getAquarium().enjoy();
  }
}

Even if the number of customers other than Family and Couple increases, the increased class only needs to implement the visit method without changing AmusementPark. It is an important idea, not limited to this pattern, to cut out the parts that are likely to change in the future. The point of the Visitor pattern is that the data structure is basically hard to change.

: question: I want to separate processes whose specifications change easily

before

public class FooController {

  private FooLatLngToPlaceAPI api;

  public String getFormattedAddress(double latitude, double longitude) {
    FooPlace place = api.getPlace(latitude, longitude);

    /*
     * 123-4567
     *Hoge City, Hoge Prefecture
     */
    return place.getPostalCode()
      + System.lineSeparator()
      + place.getPrefecture() 
      + " "
      + place.getCity();
  }
}

The specifications of the shaped part are likely to change. Since it is an example, you can easily find out what to change, but in reality there are other processes, so it can be difficult to find.

after :bulb: Strategy

public class FooController {

  private FooLatLngToPlaceAPI api;
  private FooAddressFormatter formatter = new FooAddressFormatter();

  public String getFormattedAddress(double latitude, double longitude) {
    FooPlace place = api.getPlace(latitude, longitude);

    return formatter.format(place);
  }
}

public class FooAddressFormatter {

  public String format(FooPlace place) {
    /*
     * 123-4567
     *Hoge City, Hoge Prefecture
     */
    return place.getPostalCode()
      + System.lineSeparator()
      + place.getPrefecture() 
      + " "
      + place.getCity();
  }
}

I made a class just for plastic surgery. If the formatting changes, you just need to change this class. The point is that by cutting out to a class that has only one role, it is easy to know which class and where to change when the specifications are changed. By the way, if you want to make it easier to understand what needs to be changed, but not enough to create a new class, I do the following.

public class FooController {

  private FooLatLngToPlaceAPI api;

  private static final Function<FooPlace, String> FORMAT_ADDRESS =
    place -> place.getPostalCode()
      + System.lineSeparator()
      + place.getPrefecture() 
      + " "
      + place.getCity();

  public String getFormattedAddress(double latitude, double longitude) {
    FooPlace place = api.getPlace(latitude, longitude);

    return FORMAT_ADDRESS.apply(place);
  }
}

By defining Function as a constant and writing it at the top of the class, there is no need to search for changes.

: question: I want to use similar processing properly according to the situation

before

public String getFormattedText(String text, FormatType type) {
  switch (type) {
  case BOLD:
    return "**" + text + "**";

  case ITALIC:
    return "*"  + text + "*";

  default:
    return text;
  }
}

If you want to increase the types of formatting, you have to increase the number of branches in this switch statement. Also, if the formatting method changes due to a change in the markup language, you must change this as well.

after :bulb: Strategy

public class FooFormatterFactory {

  private FooFormatterFactory() {}

  public static FooFormatter create(FormatType type) {
    switch (type) {
    case BOLD:
      return new FooBoldFormatter();

    case ITALIC:
      return new FooItalicFormatter();

    default:
      return new FooFormatter() {
        @Override
        public String format(String text) {
          return text;
        }
      }
    }
  }
}

public class FooBoldFormatter extends FooFormatter {

  @Override
  public String format(String text) {
    return "**" + text + "**";
  }
}

public class FooItalicFormatter extends FooFormatter {

  @Override
  public String format(String text) {
    return "*" + text + "*";
  }
}
public String getFormattedText(String text, FormatType type) {
  return FooFormatterFactory.create(type).format(text);
}

GetFormattedText no longer needs to be changed as the types and methods of formatting change. As in this example, Strategy is often used in combination with Factory. If you don't want to create a class for each type of formatting, combine it with a functional interface for simplicity.

public class FooFormatterFactory {

  private FooFormatterFactory() {}

  public static UnaryOperator<String> create(FormatType type) {
    switch (type) {
    case BOLD:
      return text -> "**" + text + "**";

    case ITALIC:
      return text -> "*"  + text + "*";

    default:
      return text -> text;
    }
  }
}
public String getFormattedText(String text, FormatType type) {
  return FooFormatterFactory.create(type).apply(text);
}

: question: I want to reuse some of the previous results

before

public class FooController {

  private int nextId;

  public FooResponse get(FooRequest request) {
    FooResponse response = getResponse(request);
    nextId = response.getNextId();
    return response;
  }

  public FooResponse getNext() {
    FooRequest request = new FooRequest(nextId);
    return get(request);
  }

  private FooResponse getResponse(FooRequest request) {
    //processing
    return //result
  }
}

getNext () makes the next request based on the nextId included in the previous result. If the number or type of values required when creating a request changes, you must change the values held by FooController, the processing to be held, and the processing to use the held values.

after :bulb: Memento

public class FooMemento {

  private int nextId;

  public void update(FooResponse response) {
    this.nextId = response.getNextId();
  }

  public FooRequest createNextRequest() {
    return new FooRequest(nextId);
  }
}
public class FooController {

  private FooMemento memento = new FooMemento();

  public FooResponse get(FooRequest request) {
    FooResponse response = getResponse(request);
    memento.update(response);
    return response;
  }

  public FooResponse getNext() {
    return get(memento.createNextRequest());
  }

  private FooResponse getResponse(FooRequest request) {
    //processing
    return //result
  }
}

I have created a class that holds the previous results. If the required values change when you make your next request, you only need to change FooMemento.

Recommended Posts

GoF design pattern from the problem 3. Behavior
GoF design pattern from the problem 2. Structure
GoF design pattern from the problem 1. Generation
GoF java design pattern rough summary
Learn the design pattern "Prototype" in Python
Learn the design pattern "Builder" in Python
Learn the design pattern "Flyweight" in Python
Learn the design pattern "Observer" in Python
Learn the design pattern "Memento" in Python
Learn the design pattern "Proxy" in Python
Learn the design pattern "Command" in Python
Learn the design pattern "Visitor" in Python
Learn the design pattern "Bridge" in Python
Learn the design pattern "Decorator" in Python
Learn the design pattern "Iterator" in Python
Learn the design pattern "Strategy" in Python
Learn the design pattern "Composite" in Python
Learn the design pattern "Singleton" with Python
Learn the design pattern "State" in Python
Learn the design pattern "Adapter" in Python
Learn the design pattern "Facade" with Python
Design Pattern #Builder
Design Pattern #Adapter
Design Pattern #Decorator
Learn the design pattern "Template Method" in Python
Learn the design pattern "Factory Method" in Python
Design Pattern #Facade
Design Pattern #Strategy
Design Pattern #Proxy
Learn the design pattern "Chain of Responsibility" in Python
Design Pattern #Factory Method
Logistics network design problem
Design Pattern #Template Method
About the Visitor pattern
Examine the dual problem
Ambulance placement problem --From the October issue of the OR magazine