Tech blog: www.zhenganwen.top
After the introduction of PlantUML above, I would like to record my experience in the process of learning Graphic Design Patterns in this article. Welcome to leave a comment and exchange your thoughts
Todo List
-
Adapt to Design Pattern — Adapt to Design patterns
- Iterator mode – iterate over one by one
- Adaptor mode – Add an “adapter” for reuse
-
Left to subclass — To subclass
- Template Method – passes concrete processing to subclasses
- Factory Method – Hands the generation of instances to subclasses
-
Generating Instance — Generating instances
- Singleton pattern — only one instance
- Builder pattern – Assemble complex instances
- Abstract Factory – Assembles related parts into products
-
Consider Individualy — Consider it separately
- Bridge pattern — Separates the functional hierarchy of a class from the implementation hierarchy
- Strategy pattern — Replace the algorithm as a whole
-
Consistency, Consistency
- Composite pattern — Container and content consistency
- Decorator pattern – The consistency of the decorative border to the object to be decorated
-
Access Data Structure — Access Data structures
- Visitor pattern – Accesses data structures and processes data
- Chain of Responsibility — pass the buck
-
Simplify, Simplify
- Facade pattern — Simple Windows
- Mediator mode — There is only one Mediator
-
Manage Status – Manage Status
- Observer mode – Sends notifications of status changes
- Memento mode – Saves object state
- State pattern — State is represented by classes
-
Avoid wasting
- Flyweight mode – Share objects to avoid waste
- Proxy pattern — Generate objects only when necessary
-
To Represent with Class — to Represent with classes
- Command pattern — Commands are also classes
- Interpreter pattern – Syntax rules are also classes
convention
Due to the different writing styles of each person, the expression of meaning is also different. In this paper, there are the following conventions:
- In the sample code for each design pattern,
Client
Classes represent business classes, that is, test classes that are separate from design patterns and apply them to business code. - Each class diagram in this article is used
IDEA
The plug-inPlantUML
Painted, can refer toMeeting PantUML~article - Classes in this article usually refer to classes in a broad sense, including abstract classes and interfaces
- All examples in this book are hosted in the code cloud: gitee.com/zhenganwen/…
Principle
In general, good code design follows these principles
Single Responsibility
Each class should exist only to meet a specific need. For example, the methods in a Collection class should exist to maintain the structural organization of the elements within the Collection, leaving the responsibility of traversing the elements in the Collection to the Iterator.
Single responsibility ensures that specialized tasks are handled by specialized people, so that there is only one reason for each class to change (because each class is only responsible for one responsibility), and that subsequent changes to requirements will only cause the class responsible for addressing that requirement to change, leaving the rest of the class unaffected. The less change there is, the less chance there will be bugs in the system.
Open/Closed Principle
The system should be closed for modification and open for extension. Code is designed to avoid changes to existing code, because changes to one area can cause other classes that depend on that class to change, which can introduce new potential bugs. If requirements change, the code design should accommodate the new requirements by adding new classes (mostly implementation classes), while the client code (other classes that depend on that class) should require little or no modification.
Liskov Substitution Principle
The Richter substitution principle relies on OOP polymorphism. At run time, the objects on which the client depends can be replaced by other “homologous” objects (with the same parent class or interface) without any impact on the client’s calling logic, which requires us to choose a higher level class (hierarchy of classes) when declaring the appearance type of the object (declarative type).
Interface Segregation Principle
The interface isolation principle requires that interface methods be defined on different interfaces based on their functions. For example, createItertor() should be defined on Iterable, and fly() should be defined on Flyable, which alleviates the burden of implementing classes that require unnecessary interface methods to be implemented.
Also, after Java8, interface methods should be defined as default if they have a generic implementation
Dependency Inversion Principle
The dependency inversion principle requires that we try to eliminate point-to-point “strong” dependencies and make both depend on abstractions. For example, Controller relies on AbstractService for declarative programming and XxxServiceImpl implements AbstractService abstract methods. This decouples the controller from the specific business processing class, and once the business changes, we simply add a new XxxServiceImpl2 and use polymorphism to easily switch the business processing.
Adapt to Design Pattern
This chapter will take the Iterator mode and the Adaptor mode two relatively simple as the design mode of the entry, personally experience the value of the existence of the design mode and software development should follow some principles.
The Iterator pattern
Before
Iterator refers to an Iterator that accesses the elements of a collection one by one. There are many ways to combine elements in data structures, such as groups of numbers, linked tables, hash tables, binary trees, etc. According to the different organizational forms of sets, the way we traverse and access set elements is also different. The access logic in our business code (that is, what we need to do to traverse the current element) and the traversal logic (that we need to know the internal structure of the collection) are coupled, and our business code needs to change as soon as the collection is organized in a different way.
For example, the following BookShelf organizes books in an array
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Book {
private String name;
}
public class BookShelfWithArr {
private final Book[] books;
private final int size;
private int index;
public BookShelfWithArr(int size) {
this.size = size;
this.books = new Book[size];
this.index = 0;
}
public void put(Book book) {
if (book == null) {
throw new IllegalArgumentException("book can't be null");
}
if (index == size) {
throw new RuntimeException("the bookshelf is full");
}
books[index++] = book;
}
public Book get(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("index should be equal or big than 0 but less than " + size);
}
return books[index];
}
public int size(a) {
return this.size; }}Copy the code
If you don’t use the Iterator pattern, your business code might look like this:
public class Client {
public static void main(String[] args) {
BookShelfWithArr bookShelf = new BookShelfWithArr(4);
bookShelf.put(new Book("Java"));
bookShelf.put(new Book("C"));
bookShelf.put(new Book("php"));
bookShelf.put(new Book("python"));
for (int i = 0; i < bookShelf.size(); i++) { System.out.println(bookShelf.get(i)); }}}Copy the code
This is common code when we first started learning Java collections chapters. Now let’s consider a problem. Suppose the bookshelves were made stretchable, making them smaller or bigger according to the number of books we put in them. What would we do?
Depending on the scalability, we can use ArrayList instead of array
@Data
public class BookShelf {
private ArrayList<Book> bookList;
public BookShelf(a) {
this.bookList = new ArrayList<>();
}
public BookShelf(ArrayList<Book> bookList) {
this.bookList = bookList; }}Copy the code
In this case, our traversal access logic needs to be adjusted accordingly
BookShelf bookShelf2 = new BookShelf();
bookShelf2.getBookList().add(new Book("Java"));
bookShelf2.getBookList().add(new Book("C"));
bookShelf2.getBookList().add(new Book("php"));
bookShelf2.getBookList().add(new Book("python"));
for (int i = 0; i < bookShelf2.getBookList().size(); i++) {
System.out.println(bookShelf2.getBookList().get(i));
}
Copy the code
Here, once the collection changes the way it organizes its elements, any other code that exists to traverse the collection object (referred to as client code for the collection) needs to change as well. This means that the client code and the collection are tightly coupled, and the client code should not care about how the elements are organized inside the collection, but only what it should do once it reaches the collection.
So let’s use the Iterator model to transform it
After
First, no matter how the collection organizes its elements, it should be traversable, so two methods need to be abstracted:
boolean hasNext
— Whether there are any untraversed elements in the setE next()
Fetch the next untraversed element
public interface Iterator<E> {
boolean hasNext(a);
E next(a);
}
Copy the code
Collections should focus only on how to organize elements, so should you leave the traversal logic to someone else, Iterator
public interface 可迭代<E> {
Iterator<E> iterator(a);
}
Copy the code
We can get the iterator of BookShelf by calling the iterator method of BookShelf. We can use the iterator method of BookShelf to iterate over the elements of BookShelf:
public class BookShelfIterator implements Iterator<Book> {
private int index;
private BookShelf bookShelf;
public BookShelfIterator(BookShelf bookShelf) {
this.index = 0;
this.bookShelf = bookShelf;
}
@Override
public boolean hasNext(a) {
return index < bookShelf.getBookList().size();
}
@Override
public Book next(a) {
if(! hasNext()) {throw new RuntimeException("you have arrived the end")}returnbookShelf.getBookList().get(index++); }}@Data
public class BookShelf implements 可迭代<Book> {
private ArrayList<Book> bookList;
public BookShelf(a) {
this.bookList = new ArrayList<>();
}
public BookShelf(ArrayList<Book> bookList) {
this.bookList = bookList;
}
@Override
public Iterator<Book> iterator(a) {
return new BookShelfIterator(this); }}Copy the code
Client code:
public class IteratorClient {
public static void main(String[] args) {
BookShelf bookShelf = new BookShelf();
bookShelf.getBookList().add(new Book("Java"));
bookShelf.getBookList().add(new Book("C"));
bookShelf.getBookList().add(new Book("php"));
bookShelf.getBookList().add(new Book("python"));
Iterator<Book> iterator = bookShelf.iterator();
while(iterator.hasNext()) { System.out.println(iterator.next()); }}}Copy the code
Thus, if BookShelf uses a Map to organize elements, all we need to do is add a BookShelfMapIterator, and nothing needs to be changed in the client code:
@Data
public class MapBookShelf implements 可迭代{
/** * book's name -> book */
private Map<String, Book> bookMap = new HashMap<>();
@Override
public Iterator iterator(a) {
return new MapBookShelfIterator(this); }}Copy the code
public class MapBookShelfIterator implements Iterator {
private final MapBookShelf mapBookShelf;
private final Object[] keys;
private int index;
public MapBookShelfIterator(MapBookShelf mapBookShelf) {
this.mapBookShelf = mapBookShelf;
this.keys = mapBookShelf.getBookMap().keySet().toArray();
this.index = 0;
}
@Override
public boolean hasNext(a) {
return index < keys.length;
}
@Override
public Object next(a) {
if(! hasNext()) {throw new RuntimeException("you have arrived the end");
}
returnmapBookShelf.getBookMap().get(keys[index++]); }}Copy the code
MapBookShelf bookShelf = new MapBookShelf();
bookShelf.getBookMap().put("Java".new Book("Java"));
bookShelf.getBookMap().put("C".new Book("C"));
bookShelf.getBookMap().put("PHP".new Book("php"));
bookShelf.getBookMap().put("Python".new Book("python"));
Iterator iterator = bookShelf.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
Copy the code
While the above client code has changed as well, such as getBookMap and PUT (which can be avoided by abstracting an AbstractBookShelf), the traversal access logic remains the same, namely, first fetching an Iterator instance of the collection, The interface methods hasNext and Next are then called for traversal. HasNext is a control over traversal boundaries that does nothing but determine if there are elements in the current position. Next, on the other hand, does two things: returns the element in its current position and moves the pointer back.
UML & Summarize
The complete Iterator pattern can be expressed as follows
Where Iterable corresponds to Iterator, ConcreteIterable corresponds to ConcreteIterator. The client only knows Iterator and three methods in Iterable, which can be used to realize traversal of the set regardless of the internal organization form of the set. Different collection instances hide their corresponding ConcreteIterator implementations in the createIterator method
Roles
Iterator
, iterators that are specifically responsible for iterating collections, conforming to a single responsibilityboolean hasNext()
E next()
可迭代
, the collection needs to implement the interface, delegating the traversal responsibility to a concrete iterator instanceIterator createIterator()
Collection
Collection,ConcreteIterator
, a concrete iterator, and a concrete collection instance are interdependent
Adapter pattern
Adapters exist to adapt existing implementations to different interfaces. A typical example in life is that in order to make the two plugs check to the three sockets of the socket, usually a plug converter is added between the two, and the role of the converter is the intention of this design pattern.
To adapt an existing method to a new interface (for example, if the existing method is robust and the new interface for the same function is not as repetitive as writing code), we often write an Adapter that acts only as a converter.
Adapter can be implemented through inheritance and delegation
Extends or Delegates
For example, someone else’s string-printing class Banner is left in the system
public class Banner {
public void printWithBracket(String s) {
System.out.println("(" + s + ")");
}
public void printWithStar(String s) {
System.out.println("*" + s + "*"); }}Copy the code
Now that you are iterating through the system, you need to write the implementation class for the new interface Print
public interface Print {
void printWeak(String s);
void printStrengthen(String s);
}
Copy the code
Your implementation logic and the two existing methods in the Banner collide, so you can inherit the old class and implement the new interface in a way that avoids repetitive code writing and gives some accountability to the new interface
public class PrintBanner extends Banner implements Print {
@Override
public void printWeak(String s) {
this.printWithBracket(s);
}
@Override
public void printStrengthen(String s) {
this.printWithStar(s); }}Copy the code
The disadvantage of this approach is that if the target class (in this case, Print) is not an interface, then aggregation is the only way to do it because of the single-inheritance mechanism
You can also delegate implementation logic to older classes via aggregation:
public class CustomPrint implements Print {
private Banner banner;
public CustomPrint(Banner banner) {
this.banner = banner;
}
@Override
public void printWeak(String s) {
banner.printWithBracket(s);
}
@Override
public void printStrengthen(String s) { banner.printWithStar(s); }}Copy the code
UML & Summarize
Here are the class diagrams in two ways
Mode 1 is limited by the fact that the target class must be an interface.
Roles
Adaptee
, the adaptive party is usually the old class in the system, and there is an implementation of A function A in the classTarget
The target class, usually A new class added to the system, contains abstract methods that need to implement some function AAdapter
, adaptive class, as a bridge between the adaptive and the target class, through inheritance or aggregation to achieve code reuse
Left to Subclass
Template Method
Declarative programming & Programming in the face of abstraction
The template method pattern is a special kind of “declarative” programming that abstracts the order in which the steps of a business process logic are executed, but does not care about the implementation of the steps, leaving them to the actual subclass objects of the runtime. This also takes full advantage of OOP polymorphism
For example, there is an order business class OrderService as follows:
public class OrderService {
public void makeOrder(a) {
safeVerification();
reduceStock();
reduceBalance();
noticeDelivery();
}
public void safeVerification(a) {
System.out.println("Safety check");
}
public void reduceStock(a) {
System.out.println("Go to MySQL to reduce inventory");
}
public void reduceBalance(a) {
System.out.println("Go to MySQL to reduce the balance");
}
public void noticeDelivery(a) {
System.out.println("Advise delivery"); }}Copy the code
The client code is as follows
public class Client {
public static void main(String[] args) {
OrderService orderService = newOrderService(); orderService.makeOrder(); }}Copy the code
The above code is logical and looks fine. However, if you need to cache the inventory, balance, and other information in order to execute a second kill, then you need to modify the existing OrderService. This violates the “open closed principle” (open to extensions should be closed for modifications).
At this point, we need to abstract out the business steps and delegate the specific implementation to specific subclasses to meet different business scenarios:
public abstract class AbstractOrderService {
public void makeOrder(a) {
safeVerification();
reduceStock();
reduceBalance();
noticeDelivery();
}
public void safeVerification(a){
System.out.println("Safety check");
}
public abstract void reduceStock(a) ;
public abstract void reduceBalance(a);
public void noticeDelivery(a){
System.out.println("Advise delivery"); }}Copy the code
If you need cache support, you just need to add an implementation class
public class OrderServiceWithCache extends AbstractOrderService {
@Override
public void reduceStock(a) {
System.out.println("Destocking from cache");
}
@Override
public void reduceBalance(a) {
System.out.println("Subtract balance from cache"); }}Copy the code
The client code simply switches the specific implementation class:
public class Client {
public static void main(String[] args) {
AbstractOrderService orderService = new OrderServiceWithCache(); // Use cache
orderService.makeOrder();
orderService = new OrderService(); // Switch to MySQLorderService.makeOrder(); }}Copy the code
UML & Summary
Template method pattern is done in an abstract class declarative programming (used in the form of abstract method to emphasize the completion of the business what steps should be performed as well as the steps of execution order, and don’t pay attention to every specific step is how to achieve), and to the implementation of the specific business steps to a subclasses (runtime by polymorphism).
From the client’s point of view, it is as if the specific steps of the switched instance implementation are injected into the overall business process, even though only one class instance is switched.
And for common steps, such as safeVerification and noticeDelivery above, perhaps their processing logic is fixed, then they can be extracted into a parent class for reuse.
Roles
SuperClass & Template Method
A template method is usually declared in an abstract class or interface and calls an abstract method of that class (or a non-abstract method that contains general logic) to accomplish specific business logic.Concrete SubClass & Realization
, subclasses can inject concrete functional steps into the overall business functionality by simply implementing the corresponding abstract methods, without having to call explicitly themselves (template methods are invoked dynamically based on runtime information)
Factory Method
Delay the Instance ‘s Creation
The factory method pattern is an application of the template method pattern, except that the abstract method in the abstract superclass is specialized into an instance creation method, deferring the creation of the instance to the subclass.
For example, the Person class has an abstract method getVehicle to get its own Vehicle, and can call the Vehicle exposed properties and methods in other places, while delaying the acquisition of Vehicle instances to subclasses (say delay, because it is an abstract class, can not be instantiated. Instantiating a concrete subclass of Person ensures that its getVehicle has been overridden.
Here is the sample code:
public class Vehicle {
private String name;
public Vehicle(String name) {
this.name = name;
}
public String getName(a) {
returnname; }}public class Bicycle extends Vehicle {
public Bicycle(a) {
super("Bicycle"); }}public class JeepCar extends Vehicle {
public JeepCar(a) {
super("小汽车"); }}public abstract class Person {
public void useVehicle(a) {
System.out.println("Use of transportation"+getVehicle().getName()+"Come for the ride.");
}
protected abstract Vehicle getVehicle(a);
}
public class Student extends Person {
@Override
protected Vehicle getVehicle(a) {
return newBicycle(); }}public class Boss extends Person {
@Override
protected Vehicle getVehicle(a) {
return newJeepCar(); }}public class Client {
public static void main(String[] args) {
Person p = new Student();
p.useVehicle();
p = newBoss(); p.useVehicle(); }}Copy the code
UML & Summary
Roles
Factory Method
, the abstract parent class delays the acquisition of an instance of a class to a concrete child class, but other methods in the class can call the method and think that they have obtained the instance of the class, and then access properties and methodsConcrete Method
, the actual logic to get and return the desired instance
Generating Instance
Singleton
Seven Method
-
Lazy loading
In lazy-loaded mode, instance is initialized the first time getInstance is called
public class LazyLoadingSingleton { private LazyLoadingSingleton(a) {}private static LazyLoadingSingleton instance; public static LazyLoadingSingleton getInstance(a) { if (instance == null) { instance = new LazyLoadingSingleton(); } returninstance; }}Copy the code
-
Synchronized Block Singleton
The problem of instance being assigned multiple times when the above code is executed concurrently by multiple threads can be solved by internal lock semantics Synchronized
public class SynchronizerBlockSingleton { private SynchronizerBlockSingleton(a) {}private static SynchronizerBlockSingleton instance; public static SynchronizerBlockSingleton getInstance(a) { synchronized (SynchronizerBlockSingleton.class) { if (instance == null) { instance = newSynchronizerBlockSingleton(); }}returninstance; }}Copy the code
-
Double-Checked Lock
Double-checked locking. The above code locks first and then checks, which causes context switching overhead of synchronized calling getInstance after instance is assigned. Therefore, it can be checked once before locking
public class DoubleCheckedSingleton { private DoubleCheckedSingleton(a) {}private static DoubleCheckedSingleton instance; public static DoubleCheckedSingleton getInstance(a) { if (instance == null) { synchronized (DoubleCheckedSingleton.class) { if (instance == null) { instance = newDoubleCheckedSingleton(); }}}returninstance; }}Copy the code
-
DCL with
volatile
Instruction reordering may cause the memory address of the object initialized by the new keyword to be returned before the object is completed, which may result in nullpointer exceptions during subsequent access to the instance property. Therefore, volatile is required to ensure that the reference address is returned after the object is initialized
public class VolatileSingleton { private VolatileSingleton(a) {}private static volatile VolatileSingleton instance; public static VolatileSingleton getInstance(a) { if (instance == null) { synchronized (VolatileSingleton.class) { if (instance == null) { instance = newVolatileSingleton(); }}}returninstance; }}Copy the code
-
Eager Mode
In hungry mode, if the initialization of the instance object is less expensive (memory, initialization time), it can be done at class initialization time
public class EagerSingleton { private EagerSingleton(a) {}private static EagerSingleton instance = new EagerSingleton(); public static EagerSingleton getInstance(a) { returninstance; }}Copy the code
Instance is initialized when the class is initialized, and the class is initialized only once when an active reference occurs, guaranteed by the JVM
-
Instance Holder
If you still want to use lazy mode but still want to be elegant, use static inner classes
public class InstanceHolderSingleton { private static class SingletonHolder { private static InstanceHolderSingleton instance = new InstanceHolderSingleton(); } private InstanceHolderSingleton(a) {}public static InstanceHolderSingleton getInstance(a) { returnSingletonHolder.instance; }}Copy the code
When initializing InstanceHolderSingleton SingletonHolder does not initialize the static inner class, only the call InstanceHolderSingleton. GetInstance (), Instance is initialized with SingletonHolder.
The following situations immediately cause class initialization:
- use
new
Keyword to create an instance of the class - Access the static fields of the class (including static properties and methods, but not constants)
- through
java.reflect
A class under a package reflects access to that class, such asClass.forName
- When initializing a child class, it checks whether its parent class has been initialized. If the parent class has not been initialized, the parent class is initialized first
- use
-
The elegance of enumerated classes
The JVM also guarantees that enumeration instances are initialized once when the enumeration is initialized
public class EnumSingleton { private EnumSingleton(a) {}private static EnumSingleton instance; private enum InstanceEnum{ INSTANCE; private EnumSingleton instance; InstanceEnum() { instance = newEnumSingleton(); }}public static EnumSingleton getInstance(a) { returnInstanceEnum.INSTANCE.instance; }}Copy the code
Builder
Assemble instances with complex structures
The Builder pattern is typically applied to situations that require a series of complex steps to arrive at the final instance. Just like building a house, we need to go through a series of steps, such as laying the foundation, putting up the frame, adding the brick, adding the decoration, and so on, to get a house that can live in. And, these steps can be customized, for example, some people like European and American style, then the roof of the house frame should be shaped into a cone; Some people like pink, then you can put pink wallpaper…
In this example, we demonstrate the application of Builder design pattern by generating an instance of mail content String.
public abstract class EmailBuilder {
protected String content = "";
public String getContent(a) {
return content;
}
public abstract void makeTitle(String title);
public abstract void makeBody(String body);
public abstract void makeGreeting(String greeting);
}
public class TextEmailBuilder extends EmailBuilder {
@Override
public void makeTitle(String title) {
StringBuilder stringBuilder = new StringBuilder(content);
stringBuilder.append(title).append("\n");
content = stringBuilder.toString();
}
@Override
public void makeBody(String body) {
StringBuilder stringBuilder = new StringBuilder(content);
stringBuilder.append(body).append("\n");
content = stringBuilder.toString();
}
@Override
public void makeGreeting(String greeting) {
StringBuilder stringBuilder = new StringBuilder(content);
stringBuilder.append(greeting).append("\n"); content = stringBuilder.toString(); }}public class HTMLEmailBuilder extends EmailBuilder {
@Override
public void makeTitle(String title) {
StringBuilder stringBuilder = new StringBuilder(content);
stringBuilder.append("<h3>").append(title).append("</h3>").append("\n");
content = stringBuilder.toString();
}
@Override
public void makeBody(String body) {
StringBuilder stringBuilder = new StringBuilder(content);
stringBuilder.append("<p style=\"font-family: Microsoft Ya Hei; font-size: 16px\">").append(body).append("</p>").append("\n");
content = stringBuilder.toString();
}
@Override
public void makeGreeting(String greeting) {
StringBuilder stringBuilder = new StringBuilder(content);
stringBuilder.append("<i>").append(greeting).append("</i>").append("\n"); content = stringBuilder.toString(); }}public class Director {
private EmailBuilder emailBuilder;
public Director(EmailBuilder emailBuilder) {
this.emailBuilder = emailBuilder;
}
public void construct(a) {
emailBuilder.makeTitle("About Interview");
emailBuilder.makeBody("We are honor to tell you that you can participate our interview.");
emailBuilder.makeGreeting("Good Luck!"); }}Copy the code
UML & Summary
Among them, EmailBuilder declared the initial state of the instance (empty string) and a series of process of building the instance, while TextEmailBuilder and HTMLEmailBuilder personalized realization of this series of process. Ultimately, the Director is the overseer of the entire process of building the instance, and it ensures that the instance has been built in a regular way.
Roles
Builder
, defines a series of processes (interface method) that a product needs to go throughConcreteBuilder
, personalized treatment for each processDirector
, supervisor, according to the product molding process call interface method to create productsClient
, pattern consumer, notificationDirector
According to incomingConcreteBuilder
Create a specific style of product
Abstract Factory
Assemble a product from a set of related parts
AbstractFactory is a class that contains a set of Factory methods, but the instances generated by these Factory methods are all related to each other and form a community together.
For example, if a Car needs a Car Facade, a tire Wheel, an Engine, etc., then we can create a CarFactory abstract factory that declares a series of parts to be acquired (abstract methods, regardless of the manufacturer or brand of the part). It also provides the process of building the product (calling this series of abstract methods to get the required parts and assemble them into a car)
public class Engine {
String name;
public Engine(String name) {
this.name = name; }}Copy the code
public class Wheel {
String name;
public Wheel(String name) {
this.name = name; }}Copy the code
public class Facade {
String name;
public Facade(String name) {
this.name = name; }}Copy the code
public class Car {
Engine engine;
Wheel wheel;
Facade facade;
public Car(Engine engine, Wheel wheel, Facade facade) {
this.engine = engine;
this.wheel = wheel;
this.facade = facade; }}Copy the code
public abstract class CarFactory {
public Car getCar(a) {
return new Car(getEngine(), getWheel(), getFacade());
}
public abstract Engine getEngine(a);
public abstract Wheel getWheel(a);
public abstract Facade getFacade(a);
}
Copy the code
public class CustomEngine extends Engine {
public CustomEngine(a) {
super("Custom engine"); }}Copy the code
public class CustomWheel extends Wheel{
public CustomWheel(a) {
super("Custom tires."); }}Copy the code
public class CustomFacade extends Facade {
public CustomFacade(a) {
super("Custom Car Shell"); }}Copy the code
public class CustomCarFactory extends CarFactory{
@Override
public Engine getEngine(a) {
return new CustomEngine();
}
@Override
public Wheel getWheel(a) {
return new CustomWheel();
}
@Override
public Facade getFacade(a) {
return newCustomFacade(); }}Copy the code
public class Client {
public static void main(String[] args) {
CarFactory carFactory = new CustomCarFactory();
Car car = carFactory.getCar();
System.out.println("custom car -> " + car.engine.name + "+" + car.wheel.name + "+"+ car.facade.name); }}Copy the code
UML
Consider Individualy
Bridge
Separate the functional level of the class from the implementation level of the class
-
The functional hierarchy of a class
Through inheritance we can inherit the existing functionality of the base class, and on this basis we can: rewrite the existing functionality of the base class (to make it have the characteristics of the class), can also add functionality, rewrite (rewrite the existing implementation of the base class), or the new functionality of the subclass and the base class form the class function hierarchy
public class Animal { public void eat(a) { System.out.println("Animals hunt for food."); }}public class Bird extends Animal { @Override public void eat(a) { System.out.println("Birds hunt for bugs."); } public void fly(a) { System.out.println("Birds can fly"); }}Copy the code
-
Class implementation hierarchy
Through inheritance, we can implement the abstract Method of the base class. By using Template Method, we can inject the logic of the subclass into the base class Template process. In this case, the new subclass is only to implement the abstract Method of the base class
public abstract class Animal { public void hunt(a) { lockTarget(); quickAttack(); swallow(); } public abstract void lockTarget(a); public abstract void quickAttack(a); public abstract void swallow(a); } public class Snake extends Animal { @Override public void lockTarget(a) { System.out.println("Lock on the prey."); } @Override public void quickAttack(a) { System.out.println("Quick bite on the throat."); } @Override public void swallow(a) { System.out.println("Swallow the whole thing in one gulp."); }}Copy the code
If we put the two examples of Animal together:
public abstract class Animal {
public void eat(a) {
System.out.println("Animals hunt for food.");
}
public void hunt(a) {
lockTarget();
quickAttack();
swallow();
}
public abstract void lockTarget(a);
public abstract void quickAttack(a);
public abstract void swallow(a);
}
Copy the code
As you can see, Bird does not compile. As a concrete subclass, it must implement the abstract lockTarget, quickAttack, swallow methods, but we added Bird to inherit Animal eat and add a fly function.
This is where we need to separate the functional and implementation layers of the class
public abstract class Animal {
private Hunt hunt;
public Animal(Hunt hunt) {
this.hunt = hunt;
}
public void eat(a) {
System.out.println("Animals hunt for food.");
}
public void hunt(a) { hunt.hunt(); }}public abstract class Hunt {
public void hunt(a) {
lockTarget();
quickAttack();
swallow();
}
public abstract void lockTarget(a);
public abstract void quickAttack(a);
public abstract void swallow(a);
}
public class Bird extends Animal {
public Bird(Hunt hunt) {
super(hunt);
}
@Override
public void eat(a) {
System.out.println("Birds hunt for bugs.");
}
public void fly(a) {
System.out.println("Birds can fly"); }}public class DefaultHunt extends Hunt {
@Override
public void lockTarget(a) {
System.out.println("Lock onto prey with your eyes.");
}
@Override
public void quickAttack(a) {
System.out.println("Kill your prey quickly.");
}
@Override
public void swallow(a) {
System.out.println("Bite by bite."); }}public class Snake extends Animal {
public Snake(Hunt hunt) {
super(hunt); }}public class SnakeHunt extends Hunt {
@Override
public void lockTarget(a) {
System.out.println("Infrared sensing locks on prey.");
}
@Override
public void quickAttack(a) {
System.out.println("Use fangs and venom to quickly kill prey.");
}
@Override
public void swallow(a) {
System.out.println("Swallow the whole thing in one gulp."); }}public class Client {
public static void main(String[] args) {
Hunt defaultHunt = new DefaultHunt();
Hunt snakeHunt = new SnakeHunt();
Animal snake = new Snake(snakeHunt);
System.out.println("Snake begins to hunt ==========");
snake.hunt();
Animal bird = new Bird(defaultHunt);
System.out.println("Birds start hunting ===========");
bird.hunt();
System.out.println("Birds have functions different from those of other animals."); ((Bird) bird).fly(); }}Copy the code
UML & Summary
As mentioned above, Animal, Bird and Snake form the functional hierarchy, while Hunt, DefaultHunt and Snake form the implementation hierarchy. This way, if we want to extend functionality (rewrite or add) later, we can look for class inheritance in the corresponding functionality hierarchy; If you want to personalize the hunting approach, you can simply inherit the classes in the implementation hierarchy.
The bridge pattern avoids tying implementations and extensions together, reducing the pressure on the underlying class to extend the base class
Roles
- Functional hierarchy
- Implementation hierarchy
- The base class of the functional hierarchy holds a reference to the base class of the implementation hierarchy and delegates implementation level logic to that reference
Strategy
Encapsulate a specific algorithm
Let’s say you’re a farmer and you need to pick apples that meet your customers’ standards
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Apple {
private AppleColorEnum color;
private double weight;
public enum AppleColorEnum {
RED,YELLOW,GREEN
}
}
Copy the code
So you write the following business processing class to pick Apple
public class AppleService {
List<Apple> findAppleByColor(List<Apple> apples, Apple.AppleColorEnum color) {
List<Apple> res = new ArrayList<>();
for (Apple apple : apples) {
if(Objects.equals(apple.getColor(),color)){ res.add(apple); }}returnres; }}Copy the code
But what if you get a tricky customer who not only asks for a red color, but also a weight of 500G or more? You can add a findRedAndWeightGreatThan500, but each customer might be in color and weight of the standard is different, and you can’t measure what customers, criteria for their selection.
In this case, we need to separate the selection criteria (essentially an algorithm) and analyze the inputs and outputs:
- Input:
Apple
Here is an apple - Output:
boolean
, whether the apple meets the standard
public interface AppleFilterStrategy {
/** * Return true if the apple meets the selection criteria@param apple
* @return* /
boolean filterApple(Apple apple);
}
Copy the code
At this point, picking the Apple business class eliminates the need to anticipate and preset a number of methods for picking apples, because the selection strategy is left to the AppleFilterStrategy
public class AppleService {
List<Apple> findApple(List<Apple> apples, AppleFilterStrategy strategy) {
List<Apple> res = new ArrayList<>();
for (Apple apple : apples) {
if(strategy.filterApple(apple)) { res.add(apple); }}returnres; }}Copy the code
The client can optionally inject a selection policy through anonymous classes based on the selection requirements of the client
public class Client {
public static void main(String[] args) {
// Apples picked by the farmer
List<Apple> apples = Arrays.asList(
new Apple(Apple.AppleColorEnum.RED, 200),
new Apple(Apple.AppleColorEnum.RED, 400),
new Apple(Apple.AppleColorEnum.RED, 600),
new Apple(Apple.AppleColorEnum.YELLOW, 100),
new Apple(Apple.AppleColorEnum.YELLOW, 500),
new Apple(Apple.AppleColorEnum.YELLOW, 900),
new Apple(Apple.AppleColorEnum.GREEN, 400),
new Apple(Apple.AppleColorEnum.GREEN, 500),
new Apple(Apple.AppleColorEnum.GREEN, 600)); AppleService appleService =new AppleService();
// A Customer needs red apples weighing more than 500g
List<Apple> res1 = appleService.findApple(apples, new AppleFilterStrategy() {
@Override
public boolean filterApple(Apple apple) {
return Objects.equals(apple.getColor(), Apple.AppleColorEnum.RED) &&
apple.getWeight() >= 500; }}); System.out.println(res1); System.out.println("= = = = = = = = = = = = = = = = = = = = = =");
// B Customer needs cyan less than 400
List<Apple> res2 = appleService.findApple(apples, new AppleFilterStrategy() {
@Override
public boolean filterApple(Apple apple) {
return Objects.equals(apple.getColor(), Apple.AppleColorEnum.GREEN) &&
apple.getWeight() <= 400; }}); System.out.println(res2); }}Copy the code
Functional programming
After Java8, interfaces such as AppleFilterStrategy that contain only one interface method can be labeled @functionalinterface and use Lambda expressions instead of redundant anonymous classes
@FunctionalInterface
public interface AppleFilterStrategy {
boolean filterApple(Apple apple);
}
Copy the code
List<Apple> res3 = appleService.findApple(apples, apple -> apple.getColor() ! = Apple.AppleColorEnum.GREEN && apple.getWeight() >=300);
System.out.println(res3);
Copy the code
UML & Summary
The core idea of the strategy mode is to extract the complicated and changeable algorithm logic and hand it to the client for implementation, while the business layer is only responsible for the implementation of the algorithm passed by the application client
Role
Strategy Interface
The policy interface defines a particular algorithmService
Apply policy interface algorithms to face abstract programmingClient
, the callService
Inject specific algorithm implementationConcrete Strategy
, the concrete algorithm implementation, usually in the form of anonymous classes, will be available after Java8Lambda
Instead of
Consistency
Composite
Make the container and the contents of the container look the same
A typical application of mixed mode is a File system. A Directory Directory can hold several entries, and each Entry can be either a Directory or a File. The purpose of mixed mode is to make containers (such as directories) and contents (such as directories or files) have a consistent look (such as Entry)
public abstract class Entry {
private String name;
private int size;
private List<Entry> items;
public Entry(String name, int size) {
this.name = name;
this.size = size;
items = new ArrayList<>();
}
public void addEntry(Entry entry) {
this.items.add(entry);
}
public abstract void print(int. layer);
}
public class Directory extends Entry {
public Directory(String name, int size) {
super(name, size);
}
@Override
public void print(int. layer) {
int n;
if (layer == null || layer.length == 0) {
n = 0;
} else {
n = layer[0];
}
for (int i = 0; i < n; i++) {
System.out.print("\t");
}
System.out.println(getName());
getItems().forEach(entry -> entry.print(n + 1)); }}public class File extends Entry {
public File(String name, int size) {
super(name, size);
}
@Override
public void print(int. layer) {
int n;
if (layer == null || layer.length == 0) {
n = 0;
} else {
n = layer[0];
}
for (int i = 0; i < n; i++) {
System.out.print("\t");
}
System.out.println(getName() + " size=" + getSize()+"kb");
getItems().forEach(entry -> entry.print(n + 1)); }}public class Client {
public static void main(String[] args) {
Entry root = new Directory("/root".2);
Entry bin = new Directory("/bin".0);
root.addEntry(bin);
Entry usr = new Directory("/usr".1);
root.addEntry(usr);
Entry etc = new Directory("/etc".0);
root.addEntry(etc);
Entry local = new Directory("/local".3);
usr.addEntry(local);
Entry java = new File("java.sh".128);
local.addEntry(java);
Entry mysql = new File("mysql.sh".64);
local.addEntry(mysql);
Entry hadoop = new File("hadoop.sh".1024);
local.addEntry(hadoop);
root.print();
}
}
/root
/bin
/usr
/local
java.sh size=128kb
mysql.sh size=64kb
hadoop.sh size=1024kb
/etc
Copy the code
UML
Roles
-
component
Entry, for example, is the same appearance of the container and the contents of the container. The system does not operate directly on containers and their contents, but on components
-
The container
It can contain several containers and entries
-
entry
The basic unit of a system
Decorator
Decorator pattern is a design pattern that combines inheritance and composition (delegation), enabling a uniform look and feel through inheritance (decoration and target classes have a common parent) and enhancing target functionality through delegation. And the decorator class does not care whether the target class is the source target class or the decorated target class, it does not care whether the target class has been saved, which wrapper (decorator class) wrapped, and how many layers are wrapped, it is only responsible for the current decorator logic.
You don’t change the behavior of the target class, just wrap it around
In this example, you have a Baker. He has an abstract method of baking bread, and BreadBaker is his implementation. Now we need to package the original bread according to customers’ different tastes, such as which Ingredient should be added. The following is the sample code
public abstract class Baker {
public abstract void bake(a);
}
public class BreadBaker extends Baker {
@Override
public void bake(a) {
System.out.println("Bread is baked."); }}Copy the code
public class IngredientDecorator extends Baker {
private Baker baker;
private String ingredient;
public IngredientDecorator(Baker baker,String ingredient) {
this.baker = baker;
this.ingredient = ingredient;
}
@Override
public void bake(a) {
System.out.print("Added" + ingredient + "The"); baker.bake(); }}Copy the code
public class Client {
public static void main(String[] args) {
Baker baker = new BreadBaker();
baker.bake();
Baker pepper = new IngredientDecorator(baker, "Pepper");
pepper.bake();
Baker mustard = new IngredientDecorator(baker, "Mustard");
mustard.bake();
Baker oliveOil = new IngredientDecorator(baker, "Olive oil");
oliveOil.bake();
Baker pepperAndOlive = new IngredientDecorator(new IngredientDecorator(baker, "Olive oil"), "Pepper");
pepperAndOlive.bake();
Baker mustardAndOliveAndPepper =
new IngredientDecorator(
new IngredientDecorator(
new IngredientDecorator(baker, "Pepper"),
"Olive oil"),
"Mustard"); mustardAndOliveAndPepper.bake(); }} The bread is baked and the bread is baked and the bread is baked and the bread is baked and the bread is baked and the bread is baked and the bread is baked and the bread is baked and the bread is baked and the bread is baked and the bread is baked and the bread is baked and the bread is baked and the bread is baked and the bread is baked and the bread is baked and the bread is bakedCopy the code
Of course, this example is a simple demonstration of the decorator pattern. You can specify Ingredient as PepperIngredient, MustardIngredident, and so on instead of string magic
UML & Summary
Applying decorator pattern HAS A formula: it’s you (IS-A) and you (HAS-a), all for you (all override methods delegate to the target class object, on top of which you can add the current layer of wrapped logic).
Roles
Parent
The decorator class and the target class have a common parent, giving them a common look and feel by declaring abstract methods in the parentTarget
, target class, the class that needs to be wrappedDecorator
Decoration class, which can decorate either the target object directly or decoration objects that have decorated the target object, because both have the same appearance
Access Data Structure
Visitor
Separate the management and access processing of data structures
The visitor pattern is designed to separate the management of data structures (adding, deleting, modifying and reviewing elements) from the processing logic for accessing data structures. It is often used in conjunction with the iterator pattern. We’ll define a separate Visitor interface for the logic of the data access processing and declare the corresponding VISIT (E Element) method, The data interface provides an Accept (Visitor Visitor) method that accepts the Visitor (which is simply a call to visitor.visit(). Visit (E Element) is the equivalent of consuming the elements visited.
Here is sample code for a multi-fork tree data structure (where the iterator is implemented above instead of the JDK’s own)
@Data
public abstract class Node<E> implements 可迭代<Node<E>> {
private E element;
private List<Node<E>> children;
public Node(E element) {
this.element = element;
}
public abstract void accept(NodeVisitor<E> visitor);
@Override
public Iterator<Node<E>> iterator() {
return new NodeIterator(this); }}public class Leaf<E> extends Node<E> {
public Leaf(E element) {
super(element);
this.setChildren(Collections.emptyList());
}
@Override
public void accept(NodeVisitor<E> visitor) {
visitor.visitLeaf(this); }}public class Branch<E> extends Node<E> {
public Branch(E elemnt) {
super(elemnt);
this.setChildren(new ArrayList<>());
}
@Override
public void accept(NodeVisitor<E> visitor) {
visitor.visitBranch(this); }}Copy the code
public class NodeIterator<E> implements Iterator<Node<E>> {
private Node<E> root;
private Stack<Node<E>> stack;
public NodeIterator(Node<E> root) {
this.root = root;
this.stack = new Stack<>();
stack.push(root);
}
@Override
public boolean hasNext(a) {
return stack.size() > 0;
}
@Override
public Node<E> next(a) {
if(! hasNext()) {throw new RuntimeException("no more elements");
}
Node<E> node = stack.pop();
List<Node<E>> children = node.getChildren();
for (int i = children.size() - 1; i >= 0; i--) {
stack.push(children.get(i));
}
returnnode; }}Copy the code
public interface NodeVisitor<E> {
void visitLeaf(Leaf<E> leaf);
void visitBranch(Branch<E> branch);
}
public class PrintNodeVisitor<E> implements NodeVisitor<E> {
@Override
public void visitLeaf(Leaf<E> leaf) {
System.out.print(leaf.getElement()+"");
}
@Override
public void visitBranch(Branch<E> branch) {
System.out.print(branch.getElement()+""); }}public class PlusOneNodeVisitor implements NodeVisitor<Integer> {
/** * to access the leaf node, the node value +1 *@param leaf
*/
@Override
public void visitLeaf(Leaf<Integer> leaf) {
leaf.setElement(leaf.getElement() + 1);
}
/** * if you access a forked node, the value of the node + the number of its children *@param branch
*/
@Override
public void visitBranch(Branch<Integer> branch) { branch.setElement(branch.getElement() + branch.getChildren().size()); }}Copy the code
UML & Summary
The visitor pattern delegates the access logic to the data structure via accept to the visitor interface, and the Iterator pattern delegates the traversal logic to the Iterator interface through createIterator. Assign professional work to professional personnel to fulfill the Single Responsibility and Interface Segregation Principle; The access processing logic needs to be modified. We only need to add a NodeVisitor implementation that meets the Open/Closed Principle. The logic implementation and client code are programmed toward the interface NodeVisitor to satisfy Dependency Inversion.
Roles
Visitor
Visitors declare relevantvisit
Method for accessing data structuresDataStructure
, data structure, provide element add, delete, change to check interface, throughaccept(visitor)
Pass the access processing logic toVisitor
ConcreteVisitor
According to business needs, to achieve specific access processing logic
Chain Of Responsibility
Chain of responsibility pattern forms a chain by maintaining a number of requests to accept the person to handle the client request, the model’s biggest advantage is that it weakens, the relationship between the client and request to accept the person of the client only need to send the request to the chain of responsibility, in the chain of responsibility through serial mechanism can be entrusted by the people do not accept the request directly to ignore the request, The person who can handle the request truncates the request and accepts it.
The client doesn’t have to care who receives the request, which increases the independence of the client.
Weaken the relationship between the requestor and the receiver
In this example, there is a chain of responsibility (Employee->Leader->Manager->Boss) in the organizational structure of the company. They can handle the int amount of reimbursement requests, and the Client does not need to care about the amount of reimbursement in denomination and who should handle it
public abstract class Handler {
private Handler handler = null;
public abstract void handle(int amount);
public Handler setNext(Handler handler) {
this.handler = handler;
return handler;
}
public Handler getNext(a) {
returnhandler; }}public class Employee extends Handler {
@Override
public void handle(int amount) {
if (amount <= 100) {
System.out.println("$" + amount + " is handled by employee");
return;
}
this.getNext().handle(amount); }}public class Leader extends Handler{
@Override
public void handle(int amount) {
if (amount <= 1000) {
System.out.println("$" + amount + " is handled by leader");
return;
}
this.getNext().handle(amount); }}public class Manager extends Handler{
@Override
public void handle(int amount) {
if (amount <= 5000) {
System.out.println("$" + amount + " is handled by manager");
return;
}
this.getNext().handle(amount); }}public class Boss extends Handler {
@Override
public void handle(int amount) {
System.out.println("$" + amount + " is handled by boss"); }}Copy the code
public class Client {
public static void main(String[] args) {
Handler employee = new Employee();
Handler leader = new Leader();
Handler manager = new Manager();
Handler boss = new Boss();
employee.setNext(leader).setNext(manager).setNext(boss);
for (int i = 0; i < 6; i++) {
int amount = (int) (Math.random() * 10000); employee.handle(amount); }}} $6643 is handled by boss
$4964 is handled by manager
$684 is handled by leader
$9176 is handled by boss
$8054 is handled by boss
$909 is handled by leader
Copy the code
The resources
Graphic Design Patterns