Dies ist die Struktur der GoF-Entwurfsmusterreihe aus der Perspektive von Problemen.
before
FooAPI fooAPI = new FooAPI(lat, lng);
FooPlace place = new FooPlace();
place.setAddress(fooAPI.getPostalCode() + " " + fooAPI.getAddress()); // 〒012-3456 Hoge City, Präfektur Hoge
place.setStation(
new StringJoiner(",")
.add(fooAPI.getStation1())
.add(fooAPI.getStation2())
.add(fooAPI.getStation3())
.toString()); //Diese Station,Kakiku Station,Sashisu Station
Dies ist ein Fall, in dem das Ergebnis der API formatiert und verwendet wird. Dies ist der einzige Ort, an dem die FooAPI verwendet wird. Wenn sich die Formungsmethode in Zukunft nicht ändert, ist dies in Ordnung. In Anbetracht der Wartbarkeit ist dies jedoch kein gutes 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());
Wir haben einen Adapter zum Formatieren von API-Antworten vorbereitet. Wenn sich die Formatierungsmethode ändert, müssen Sie nur diesen Adapter ändern, was sich nicht auf den Benutzer auswirkt.
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();
Der schlechte Punkt ist, dass der Benutzer die Prozedur zum Erstellen einer Instanz kennen muss → 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);
Der Benutzer muss nicht über die Bestellung nachdenken. In dieser Größenordnung können Sie in einer Zeile schreiben, sodass Sie die Methoden nicht in Facade unterteilen müssen. Dies ist jedoch ein Beispiel. Schauen Sie also genau hin. Facade ist in erster Linie nützlich, um komplexe Prozesse zu gruppieren, bei denen mehrere Klassen nacheinander verwendet werden.
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);
Dies ist ein einfaches und gutes Design für sich, aber nehmen wir an, Sie möchten eine Klasse erstellen, die Bewegungen in x- und y-Richtung gleichzeitig verarbeitet. (Das Design, das die Operation des Werts von FooPosition in FooMove unterteilt und mit moveAs akzeptiert, ist übrigens das Besuchermuster.)
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);
Die Bewegungen von x und y wurden zusammengefasst.
Außerdem müssen Sie bei diesem Design nur "new FooMoveHorizontal (x, move)" verwenden, um weitere Züge hinzuzufügen. (Rufen Sie position.moveAs
nur einmal auf)
Auf dieser Skala ist es einfacher zu verstehen, ob Sie eine Klasse erstellen, die gleichzeitig x und y verarbeitet, aber da es sich um ein Beispiel handelt (weggelassen)
before
public class FooTestUser extends FooUser {
@Override
public void foo1() {
//Normaler Benutzer 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() {
//Normaler Benutzer foo1
}
@Override
public void foo2() {
//Normaler Benutzer 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() {
//Verarbeitung von foo3
}
}
TestUser möchte seine Funktionalität einschränken, damit nur NormalUser foo1 ausgeführt werden kann. Zu diesem Zeitpunkt möchte ich verwalten, dass die Implementierung von foo1 von TestUser und NormalUser zu einer Kopie wird.
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 und Super User sind die gleichen wie zuvor
Es gibt kein Kopieren und Einfügen der Implementierung. Wenn sich die Anforderungen von foo1 ändern, müssen Sie nur den normalen Benutzer ändern. Da Java 8 jedoch eine Standardimplementierung der Schnittstelle hinzugefügt hat, ist es möglicherweise besser, wenn Sie nur die Implementierung freigeben möchten. (Gleiches gilt für das Brückenmuster) Wenn Sie keinen SuperUser haben, ist es meiner Meinung nach einfacher, foo1 in der übergeordneten Klasse (FooUser) zu implementieren.
Recommended Posts