This is the behavior of the GoF design pattern series from the perspective of problems.
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.
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.
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);
}
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