[JAVA] GoF design pattern from the problem 2. Structure

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

  1. Generation
  2. Structure
  3. Behavior

: question: I want the API response to be domain-friendly

before

FooAPI fooAPI = new FooAPI(lat, lng);
FooPlace place = new FooPlace();
place.setAddress(fooAPI.getPostalCode() + " " + fooAPI.getAddress()); // 〒012-3456 Hoge City, Hoge Prefecture
place.setStation(
  new StringJoiner(",")
    .add(fooAPI.getStation1())
    .add(fooAPI.getStation2())
    .add(fooAPI.getStation3())
    .toString());  //That station,Kakiku station,Sashisu Station

This is a case where the result of API is formatted and used. This is the only place where the FooAPI is used, and if the shaping method does not change in the future, this is fine, but considering maintainability, it is not a good design.

after :bulb: Adapter

public class FooAPIAdapter {

  private final FooAPI fooAPI;

  public FooAPIAdapter(double latitude, double longitude) {
    this.fooAPI = new FooAPI(latitude, longitude);
  }

  public String getAddress() {
    return fooAPI.getPostalCode() + fooAPI.getAddress();
  }

  public String getStation() {
    return new StringJoiner(",")
      .add(fooAPI.getStation1())
      .add(fooAPI.getStation2())
      .add(fooAPI.getStation3())
      .toString();
  }
}
FooAPIAdapter fooAPI = new FooAPIAdapter(lat, lng);
FooPlace place = new FooPlace();
place.setAddress(fooAPI.getAddress());
place.setStation(fooAPI.getStation());

We have prepared an Adapter to format the API response. If there is a change in the formatting method, you only need to modify this Adapter and it will not affect the user.

: question: I don't want the API users to think about the order of operations

before

public class FooSorter {

  private List<FooStudent> students = new List<>();

  public void add(FooStudent student) {
    students.add(student);
  }

  public void sort() {
    students.sort(
      Comparator.comparingInt(
        student -> student.getJapaneseScore()
          + student.getMathScore()
          + student.getEnglishScore())
      .reversed());
  }

  public List<FooStudent> getResult() {
    return students;
  }
}
FooSorter sorter = new FooSorter();
sorter.add(student1);
sorter.add(student2);
sorter.add(student3);
sorter.sort();
sorter.getResult();

The bad point is that the user must know the procedure of instance creation → add () → sort () → getResult ().

after :bulb: Facade

public class FooSorter {

  private List<FooStudent> students = new List<>();

  private FooSorter() {}

  public static List<FooStudent> sort(FooStudent... students) {
    for (FooStudent student : students)
      add(student);

    sort();
    return getResult();
  }

  private void add(FooStudent student) {
    students.add(student);
  }

  private void sort() {
    students.sort(
      Comparator.comparingInt(
        student -> student.getJapaneseScore()
          + student.getMathScore()
          + student.getEnglishScore())
      .reversed());
  }

  private List<FooStudent> getResult() {
    return students;
  }
}
FooSorter.sort(student1, student2, student3);

The user does not have to think about the order. At this scale, you can write in one line, so you don't have to separate the methods into Facade, but it's an example, so please take a good look. Facades are primarily useful for grouping complex processes that use multiple classes in sequence.

: question: I want to return the result of multiplying the results of sibling classes

before

public class FooPosition {

  private int x;
  private int y;

  // x,y accessor

  public void moveAs(FooMove move) {
    move.move(this);
  }
}

public abstract class FooMove {

  private final int addition;

  public FooMove(int addition) {
    this.addition = addition;
  }

  public abstract void move(FooPosition position);
}

public class FooMoveHorizontal extends FooMove {

  public FooMoveHorizontal(int addition) {
    super(addition);
  }
  
  @Override
  public void move(FooPosition position) {
    position.setX(position.getX() + addition);
  }
}

public class FooMoveVertical extends FooMove {

  public FooMoveVertical(int addition) {
    super(addition);
  }

  @Override
  public void move(FooPosition position) {
    position.setY(position.getY() + addition);
  }
}
FooMove moveHorizontal = new FooMoveHorizontal(x);
FooMove moveVertical = new FooMoveVertical(y);

FooPosition position = new FooPosition();
position.moveAs(moveHorizontal);
position.moveAs(moveVertical);

This is a simple and good design in itself, but suppose you want to create a class that handles movements in the x and y directions at the same time. (By the way, the design that divides the operation of the value of FooPosition into FooMove and accepts it with moveAs is the Visitor pattern)

after :bulb: Decorator

public class FooPosition {

  private int x;
  private int y;

  // x,y accessor

  public void moveAs(FooMove move) {
    move.move(this);
  }
}

public abstract class FooMove {

  private final int addition;
  private final FooMove move;

  public FooMove(int addition) {
    this.addition = addition;
  }

  public FooMove(int addition, FooMove move) {
    this.addition = addition;
    this.move = move;
  }

  public abstract void move(FooPosition position);
}

public class FooMoveHorizontal extends FooMove {

  public FooMoveHorizontal(int addition) {
    super(addition);
  }

  public FooMoveHorizontal(int addition, FooMove move) {
    super(addition, move);
  }
  
  @Override
  public void move(FooPosition position) {
    if (move != null)
      move.move(position);

    position.setX(position.getX() + addition);
  }
}

public class FooMoveVertical extends FooMove {

  public FooMoveVertical(int addition) {
    super(addition);
  }

  public FooMoveVertical(int addition, FooMove move) {
    super(addition, move);
  }

  @Override
  public void move(FooPosition position) {
    if (move != null)
      move.move(position);

    position.setY(position.getY() + addition);
  }
}
FooMove move = new FooMoveHorizontal(x, new FooMoveVertical(y));

FooPosition position = new FooPosition();
position.moveAs(move);

The movements of x and y have been summarized. Also, with this design, you only need to use new FooMoveHorizontal (x, move) to add more moves. (Call position.moveAs only once) At this scale, it is easier to understand if you create a class that handles x and y at the same time, but since it is an example (omitted)

: question: I want to share the implementation with sibling classes (or I want to limit the function according to the condition)

before

public class FooTestUser extends FooUser {

  @Override
  public void foo1() {
    //Normal User foo1
  }

  @Override
  public void foo2() {
    throw new RuntimeException("this operation is not permitted.");
  }

  @Override
  public void foo3() {
    throw new RuntimeException("this operation is not permitted.");
  }
}

public class FooNormalUser extends FooUser {

  @Override
  public void foo1() {
    //Normal User foo1
  }

  @Override
  public void foo2() {
    //Normal User foo2
  }

  @Override
  public void foo3() {
    throw new RuntimeException("this operation is not permitted.");
  }
}

public class FooSuperUser extends FooUser {

  @Override
  public void foo1() {
    //SuperUser foo1
  }

  @Override
  public void foo2() {
    //SuperUser foo2
  }

  @Override
  public void foo3() {
    //Processing of foo3
  }
}

TestUser wants to limit its functionality so that only NormalUser foo1 can be executed. At this time, I want to manage that the implementation of foo1 of TestUser and NormalUser becomes a copy.

after :bulb: Proxy

public class FooTestUser extends FooUser {

  private FooUser normalUser = new FooNormalUser();

  @Override
  public void foo1() {
    normalUser.foo1();
  }

  @Override
  public void foo2() {
    throw new RuntimeException("this operation is not permitted.");
  }

  @Override
  public void foo3() {
    throw new RuntimeException("this operation is not permitted.");
  }
}

//Normal User and Super User are the same as before

There is no longer a copy and paste of the implementation, and if the requirements of foo1 change, you only need to change the Normal User. However, since Java 8 has added a default implementation of interface, it may be better if you just want to share the implementation. (Same for Bridge pattern) If you don't have SuperUser, I think it's simpler to implement foo1 in the parent class (FooUser).

Recommended Posts

GoF design pattern from the problem 2. Structure
GoF design pattern from the problem 1. Generation
GoF design pattern from the problem 3. Behavior
GoF java design pattern rough summary
Learn the design pattern "Prototype" in Python
Learn the design pattern "Flyweight" 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 "Mediator" 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 #Adapter
Learn the design pattern "Abstract Factory" in Python
Design Pattern #Decorator
Learn the design pattern "Template Method" in Python
Design Pattern #Observer
Learn the design pattern "Factory Method" in Python
Design Pattern #Facade
Design Pattern #Strategy
Design Pattern #Singleton
Design Pattern #Proxy
Calculate volume from the two-dimensional structure of a compound
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
GoF design pattern is just an "introduction of abstraction layer"
Ambulance placement problem --From the October issue of the OR magazine