This article is from:Blog.csdn.net/anxpp/artic…Click here to see the original article
The sample source code in this article is available on Github: github.com/anxpp/JavaD…
Because the CSDN dropdown page turning is more troublesome, hereby reprint!
Design pattern introduction and Java description
An overview of the
A design pattern is an optimal solution to a problem and is derived from many excellent software systems.
There are typically 23 Java Design Patterns.
Patterns can be divided into three categories: creative, behavioral, and structural.
Creation pattern
The creation pattern involves the instantiation of objects and is characterized by the fact that the user code is not dependent on how objects are created or arranged, so that the user does not directly create objects using new.
There are five creation patterns:
Factory method pattern, Abstract Factory method pattern, generator pattern, prototype pattern, and singleton pattern.
Behavioral pattern
The behavior pattern is concerned with how to design the interaction and communication between objects reasonably, and how to assign responsibilities to objects reasonably, so that the design is flexible, easy to maintain, and easy to reuse.
There are 11 behavioral patterns:
Chain of Responsibility pattern, command pattern, interpreter pattern, iterator pattern, mediator pattern, memo pattern, observer pattern, state pattern, policy pattern, template method pattern and visitor pattern.
Structural mode
Structural patterns are concerned with how to combine classes and objects to form larger structures. Class-related structural patterns are concerned with how to use inheritance mechanisms properly. The structural patterns associated with objects are concerned with the proper use of object composition mechanisms.
There are seven structural modes:
Adapter mode, composite mode, proxy mode, share mode, appearance mode, bridge mode, and decoration mode.
Let’s take a look at each one
1. Singleton Pattern
Ensure a class only has one instance,and provide a global point of access to it. Ensure that a class has only one instance and provide a global access point to access it.Copy the code
When to use
-
Advantages when the system needs only one instance of a class
-
The class unique instance of the singleton pattern is controlled by itself and has good control over when users access it.
The singleton pattern concept is simple and common.
When using this pattern, we need to consider whether it will be used in multithreading. If not, it is simple enough:
public class SimpleSingleton {
private static SimpleSingleton instance;
private SimpleSingleton(a){}
public static SimpleSingleton getIntance(a){
if(instance == null)
instance = new SimpleSingleton();
returninstance; }}Copy the code
The above example is a simple singleton pattern implementation using lazy loading pattern. However, multiple instances can be created in multiple threads. Here is an introduction to the use of multithreading.
If you applied the above example directly to multithreading, you could simply set getInstance() to synchronized, but that’s not efficient. After any thread, only one thread can call the method, and the rest queue up.
So it’s not optimal to synchronize the whole method, so just synchronize the code block. This leads to a double-checked lock, where null is checked once outside the synchronized block and again inside the synchronized block. But ultimately this approach can be problematic, and using static inner classes is a better approach.
Singleton patterns are frequently used and simple, but not always written correctly. For details, see how to Write Singleton patterns correctly. Inside the detailed analysis of the singleton pattern of writing methods.
There is a lot of sample code in other patterns that uses the singleton pattern, so I won’t add additional examples here.
Here is a recommended implementation (enumeration) :
public enum EasySingleton{
INSTANCE;
}
Copy the code
2. Factory Method Pattern
Alias: Another Name: Virtual Constructor Define an interface for creating an object,but let subclasses decide which class to instantiate.Factory Method lets a class defer instantiation to subclassess. Define an interface for creating objects and let subclasses decide which class to instantiate. The factory method delays the instantiation of a class to its subclasses.Copy the code
When to use
- The user needs an instance of a subclass of a class, but does not want to be coupled to a subclass of that class
- The user needs an instance of a subclass of a class, but the user does not know which subclasses are available for that class
advantages
- Using factory methods decouples the user’s code from that of a subclass of a particular class
- The factory method does not require the user to know how the object it uses was created, just what methods it has.
Simple Factory model
Before introducing the Factory method pattern, I will introduce the Simple Factory pattern, which is also a factory method pattern.
The simple factory pattern is also called the static factory method pattern. The naming suggests that this pattern must be simple. It exists for a simple purpose: to define an interface for creating objects.
If a number of objects (products) are already identified and cannot easily be changed and new products added, the simple factory pattern can be used for a long time. Here is an example of a simple factory:
// Demonstrate a simple factory
public class SimpleFactory {
public static void main(String args[]) throws Exception{
Factory factory = new Factory();
factory.produce("PRO5").run();
factory.produce("PRO6").run(); }}// Abstract product
interface MeizuPhone{
void run(a);
}
// Specific product X2
class PRO5 implements MeizuPhone{
@Override
public void run(a) {
System.out.println("I'm a PRO5."); }}class PRO6 implements MeizuPhone{
@Override
public void run(a) {
System.out.println("I'm a PRO6."); }}/ / factory
class Factory{
MeizuPhone produce(String product) throws Exception{
if(product.equals("PRO5"))
return new PRO5();
else if(product.equals("PRO6"))
return new PRO6();
throw new Exception("No Such Class"); }}Copy the code
It is easy to see that the simple factory pattern is not easy to maintain, and if new products need to be added, the entire system needs to be modified. If we need to add products such as PRO7 and PRO8, we can add them directly in the engineering class. But if the root doesn’t know what else exists until the subclass is implemented, then the factory method pattern is needed.
In practice, the product is likely to be a multi-level tree structure. Since there is only one factory class for these products in the simple factory pattern, it is quite cumbersome to implement, so the factory method pattern formally solves this problem, which is described below.
Factory method pattern
The factory method pattern removes the static properties of the factory method from the simple factory pattern, making it inheritable by subclasses. Thus the stress of focusing on the factory method in the simple factory pattern can be shared by the different factory subclasses in the factory method pattern.
For the example above, if you use the factory method pattern, where the factory is defined as an interface and the specific factory determines what products need to be generated, here is the code for comparison with the simple factory:
// Factory method pattern
public class FactoryMethod {
public static void main(String args[]){
IFactory bigfactory;
bigfactory = new SmallFactory();
bigfactory.produce().run();
bigfactory = newBigFactory(); bigfactory.produce().run(); }}// Abstract product
interface MeizuPhone{
void run(a);
}
// Specific product *2
class PRO5 implements MeizuPhone{
@Override
public void run(a) {
System.out.println("I'm a PRO5."); }}class MX5 implements MeizuPhone{
@Override
public void run(a) {
System.out.println("I'm an MX5."); }}interface IFactory{// Abstract factory
MeizuPhone produce(a);
}
/ / * 2 factory
class BigFactory implements IFactory{
@Override
public MeizuPhone produce(a) {
return newPRO5(); }}class SmallFactory implements IFactory{
@Override
public MeizuPhone produce(a) {
return newMX5(); }}Copy the code
If you know anything about Java’s collections framework, this is a good example:
All implementations of the Collection interface in Java can return an iterator through the iterator() method. The iterators of different implementations are implemented as inner classes on the iterator interface in that implementation and then returned through the iterator() method. The iterator() method, then, is a factory method.
We can see that the abstract product is the Iterator interface. The concrete product is the implementation of the Iterator interface in the implementation of the Collection interface. The constructor is the Collection interface, which provides the factory method Iterator Iterator (). The concrete constructor is the implementation of Collection. The structure of the factory method pattern is made up of four parts in bold.
If you’re not familiar with Java containers, here’s another example (modeled after Iterator, which is also introduced incidentally) :
If we have multiple data structures to iterate over, we need a tool to iterate over different structures. First, we need to define an interface (abstract product) for the tool that describes how to iterate:
// Just need to iterate over a bunch of data, so only 2 methods are needed
public interface Iterator<T> {
boolean hasNext(a); // If there is a next element
T next(a); // Get the next element
}
Copy the code
Then there are the objects we want to traverse, and these objects are tentatively named lists, which are the constructors:
// Easy to introduce, do not do many operations
public interface List<T> {
Iterator<T> iterator(a); // Return a traverser
boolean add(T t); // Add elements to the list
}
Copy the code
There are many possible implementations of lists, such as arrays and linked lists, which are briefly introduced here. These are concrete constructors, and there are concrete implementations of traversers (concrete products). Here the form of the inner class is put into the List implementation (concrete constructors). It is also entirely possible to modify the code to separate the implementation (product-specific) of the traverser:
Array implementation:
package com.anxpp.designpattern.factorymethod;
// A simple array list implemented for demonstration purposes
public class ArrayList<T> implements List<T>{
private int size; // The number of stored elements is initialized to 0 by default
private Object[] defaultList; // Use arrays to store elements
private static final int defaultLength = 10;// The default length
public ArrayList(a){ // Default constructor
defaultList = new Object[defaultLength];
}
@Override
public Iterator<T> iterator(a) {
return new MyIterator();
}
// Add elements
@Override
public boolean add(T t) {
if(size<=defaultLength){
defaultList[size++] = t;
return true;
}
return false;
}
// Traversal (specific product)
private class MyIterator implements Iterator<T>{
private int next;
@Override
public boolean hasNext(a) {
return next<size;
}
@SuppressWarnings("unchecked")
@Override
public T next(a) {
return(T) defaultList[next++]; }}}Copy the code
Linked list implementation:
// A simple list of one-way lists implemented for easy demonstration
public class LinkList<T> implements List<T>{
private int size; // The number of stored elements is initialized to 0 by default
private Node<T> first; // The first node is initialized to null by default
@Override
public Iterator<T> iterator(a) {
return new MyIterator();
}
@Override
public boolean add(T t) {
if(size==0){
first = new Node<T>(t,null);
size++;
return true;
}
Node<T> node = first;
while(node.next! =null)
node = node.next;
node.next = new Node<T>(t,null);
size++;
return true;
}
// List nodes
private static class Node<T>{
T data;
Node<T> next;
Node(T data,Node<T> next){
this.data = data;
this.next = next; }}/ / traverse
private class MyIterator implements Iterator<T>{
private Node<T> next; // Next node
MyIterator(){
next = first;
}
@Override
public boolean hasNext(a) {
returnnext ! =null;
}
@Override
public T next(a) {
T data = next.data;
next = next.next;
returndata; }}}Copy the code
Use the code above (pattern use) :
package com.anxpp.designpattern.factorymethod;
public class TestUse {
public static void main(String args[]){
// Define the two structures respectively
List<Integer> array = new ArrayList<Integer>();
List<Integer> link = new LinkList<Integer>();
// Add data
for(int i = 1; i <8; i++){
array.add(i);
link.add(i);
}
// Get the iterator
Iterator<Integer> ai = array.iterator();
Iterator<Integer> li = link.iterator();
// Iterate and print
while(ai.hasNext())
System.out.print(ai.next());
System.out.println();
while(li.hasNext()) System.out.print(li.next()); }}Copy the code
The console will print:
1234567, 1234567,Copy the code
This is the factory method pattern, where the traversator is also an iterator design pattern, described later. I’m not going to tell you about building cars, ships, people, I’m going to give you practical applications. This is just one example of an application where a series of implementations of an interface need other objects to do the same thing: define methods that return another object in this interface (factory methods), and then return objects that operate on it in the implementation of that interface.
The above example shows the complete implementation code in iterator mode.
Multiple concrete product classes are derived from an abstract product class; Several concrete factory classes are derived from an abstract factory class. Only one instance of a concrete product class can be created per concrete factory class. That is, define an interface to create an object (the abstract factory class) and let its subclasses (the concrete factory class) decide which class (the concrete product class) to instantiate. A one-to-one relationship.
The trade-off against simple factory: The difference in definition between the factory method pattern and simple factory pattern is obvious. The core of the factory method pattern is an abstract factory class, unlike the simple factory pattern, which places the core on a real class. The factory method pattern extends the simple factory pattern by allowing many real factory classes to be inherited from abstract factory classes, which can in effect be a synthesis of multiple simple factory patterns. On the other hand, the simple factory pattern is a degeneration of the factory method pattern. Imagine if we were pretty sure that a system only needed a real factory class, then we might as well merge the abstract factory class into the real factory class. In this case, we are reduced to a simple factory model.
You can see that the addition of the factory method multiplies the number of objects. When the product category is very large, there will be a large number of factory objects corresponding to it, which is not desirable.
If the division is more detailed, a factory may not only produce mobile phones (xiaomi, for example, makes rice cookers as well as mobile phones), but a factory may produce low-end products intelligently, while a larger factory may often produce higher-end products. Therefore, a factory is not enough, in this case, should use the abstract factory to solve the problem.
3. Abstract Factory Pattern
Aliases: matching (Another Name: Kit) Provide an interface for creating families of related or dependent objects without specifying their concrete classess. Provides an interface for creating a series of or interdependent objects without specifying their concrete classes.Copy the code
In the above example of generating Meizu products, we only produce mobile phones, but there may be other products, such as earphones. In order to generate earphones, we need to expand the above example.
To compare these patterns, let’s start with the extended abstract factory pattern code from the phone generation example above:
// Abstract factory schema
public class AbstractFactory {
public static void main(String args[]){
IFactory bigfactory = new BigFactory();
IFactory smallfactory = newBigFactory(); bigfactory.producePhone().run(); bigfactory.produceHeadset().play(); smallfactory.producePhone().run(); smallfactory.produceHeadset().play(); }}// Abstract product *2
interface Headset{
void play(a);
}
// Abstract product
interface MeizuPhone{
void run(a);
}
// Specific product *2*2
class PRO5 implements MeizuPhone{
@Override
public void run(a) {
System.out.println("I'm a PRO5."); }}class MX5 implements MeizuPhone{
@Override
public void run(a) {
System.out.println("I'm an MX5."); }}class EP21 implements Headset{
@Override
public void play(a) {
System.out.println("I'm an EP21."); }}class EP30 implements Headset{
@Override
public void play(a) {
System.out.println("I am an EP30"); }}// Abstract factory
interface IFactory{
MeizuPhone producePhone(a);
Headset produceHeadset(a);
}
// Specific factory *2
class BigFactory implements IFactory{
@Override
public MeizuPhone producePhone(a) {
return new PRO5();
}
@Override
public Headset produceHeadset(a) {
return newEP30(); }}// Specific factory *2
class SmallFactory implements IFactory{
@Override
public MeizuPhone producePhone(a) {
return new MX5();
}
@Override
public Headset produceHeadset(a) {
return newEP21(); }}Copy the code
In the abstract factory pattern, an AbstractProduct may be one or more, thus constituting one or more Product families. In the case of only one product family, the abstract factory pattern actually degrades to the factory method pattern (as in the example above, when you subtract the headset product, you go back to the factory method pattern).
This is an empty example, but it is only to compare the three patterns, and to give abstract examples it is easier to see the difference.
So the above example is an example of production iterators in action, and here we also align extensions to introduce the abstract factory pattern. Iterators are exclusive to collections, but now we want to produce iterators for maps. As we all know, maps do not inherit from collections, and the way we iterate is different. This is equivalent to two product families.
To demonstrate how we can implement this iterator that produces both Map and Collection, I will post the example step by step:
The first is an abstract product that describes a common interface for iterators:
// Abstract product
public interface IIterator<T> {
boolean hasNext(a);
Object next(a);
}
Copy the code
Then there is the abstract factory, which returns different iterators:
// Abstract factory
public interface IIteratorFactory<T> {
IIterator<T> iteratorMap(Map<T,Object> m);
IIterator<T> iteratorCollection(Collection<T> c);
}
Copy the code
Next is the product.
Iterators for Collection (specific product) :
// Product specific, Collection iterator (using proxy mode)
public class IteratorCollection<T> implements IIterator<T>{
Iterator<T> iterator;
public IteratorCollection(Collection<T> c){
iterator = c.iterator();
}
@Override
public boolean hasNext(a) {
return iterator.hasNext();
}
@Override
public T next(a) {
returniterator.next(); }}Copy the code
Iterators for Map (specific product) :
// Specific product, Map iterator (using proxy mode)
public class IteratorMap<T> implements IIterator<T>{
Iterator<Map.Entry<T, Object>> iterator;
public IteratorMap(Map<T,Object> m){
iterator = m.entrySet().iterator();
}
@Override
public boolean hasNext(a) {
return iterator.hasNext();
}
@Override
public Object next(a) {
returniterator.next().getValue(); }}Copy the code
After completing the specific product design, we will realize the specific factory:
// Specific factory
public class IteratorFactory<T> implements IIteratorFactory<T>{
@Override
public IteratorMap<T> iteratorMap(Map<T,Object> m) {
return new IteratorMap<T>(m);
}
@Override
public IteratorCollection<T> iteratorCollection(Collection<T> c) {
return newIteratorCollection<T>(c); }}Copy the code
At this point, the little framework is complete and we can use it to iterate over collections (lists, sets, queues are all integrated from it) and maps:
// Test use
public class TestUse {
public static void main(String args[]){
IIteratorFactory<Integer> factory = new IteratorFactory<>();
Collection<Integer> collection = new ArrayList<Integer>();
Map<Integer, Object> map = new LinkedHashMap<>();
for(int i=0; i<10; i++){ collection.add(i); map.put(i, i); } IIterator<Integer> iteratorCollection = factory.iteratorCollection(collection); IIterator<Integer> iteratorMap = factory.iteratorMap(map);while(iteratorCollection.hasNext())
System.out.print(iteratorCollection.next());
System.out.println();
while(iteratorMap.hasNext()) System.out.print(iteratorMap.next()); }}Copy the code
Output:
0123456789, 0123456789,Copy the code
In practice, we probably shouldn’t do this, thinking of Collection as a container for one object and Map as an associative container for two objects, but this example uses the abstract factory pattern to achieve a uniform way of traversing different containers.
If a container holds a large number of objects that are either directly or indirectly integrated into a class, it is also a good idea to use the visitor pattern for traversal, as described later in the Visitor pattern.
The factory model mainly involves the above three types:
- The simple factory pattern is a concrete class that creates instances of other classes, whose parent is the same and whose parent is concrete.
- The factory method pattern has an abstract parent class defining the common interface, and the subclass is responsible for generating concrete objects. The purpose of this pattern is to defer the instantiation of the class to the subclass.
- The abstract factory pattern provides an interface to create a series of related or interdependent objects without specifying their concrete classes. It targets hierarchical structures with multiple products. The factory method pattern targets the hierarchical structure of a product.
4. Builder Pattern
Separate the construction of a complex object from its representation so that the same construction process can create different representations. Separating the construction of a complex object from its representation enables the same construction process to create different representations.Copy the code
When to use:
- When the system is ready to provide the user with an object with a complex internal structure, and the code that creates the object in the constructor does not satisfy the user’s needs, the generator pattern can be used to construct such an object.
- When some systems require that the object’s construction process be independent of the class that created the object.
Advantages:
- The generator pattern encapsulates the construction process of an object in a concrete generator, and users can obtain different representations of the object using different concrete generators.
- The generator pattern separates the construction of an object from the class that created it, freeing the user from knowledge of the object’s specific components.
- The construction process of objects can be controlled more finely and effectively. The generator breaks down the construction of an object into several steps, so that the program can control the construction of the entire object more finely and effectively.
- The generator pattern decouples the process of constructing an object from the class that created it, making object creation more flexible and elastic.
- When adding a new specific generator, there is no need to modify the commander’s code, that is, the pattern satisfies the open-closed principle.
The focus of the pattern is to separate the build algorithm from the concrete build implementation so that the build algorithm can be reused.
For example, if we want to get a date, we can have different formats, and then we use different generators to do that.
/ / product
public class MyDate {
String date;
}
Copy the code
Then there is the abstract generator, which describes the behavior of the generator:
// Abstract generator
public interface IDateBuilder {
IDateBuilder buildDate(int y,int m,int d);
String date(a);
}
Copy the code
Here are the concrete generators, one with a “-” to separate the year, month and day, and another with Spaces:
// Specify the generator
public class DateBuilder1 implements IDateBuilder{
private MyDate myDate;
public DateBuilder1(MyDate myDate){
this.myDate = myDate;
}
@Override
public IDateBuilder buildDate(int y, int m, int d) {
myDate.date = y+"-"+m+"-"+d;
return this;
}
@Override
public String date(a) {
returnmyDate.date; }}Copy the code
// Specify the generator
public class DateBuilder2 implements IDateBuilder{
private MyDate myDate;
public DateBuilder2(MyDate myDate){
this.myDate = myDate;
}
@Override
public IDateBuilder buildDate(int y, int m, int d) {
myDate.date = y+""+m+""+d;
return this;
}
@Override
public String date(a) {
returnmyDate.date; }}Copy the code
Next comes the commander, who provides the user with a specific generator:
/ / commanders
public class Derector {
private IDateBuilder builder;
public Derector(IDateBuilder builder){
this.builder = builder;
}
public String getDate(int y,int m,int d){
builder.buildDate(y, m, d);
returnbuilder.date(); }}Copy the code
Use as follows:
public class TestUse {
public static void main(String args[]){
MyDate date = new MyDate();
IDateBuilder builder;
builder = new DateBuilder1(date).buildDate(2066.3.5);
System.out.println(builder.date());
builder = new DateBuilder2(date).buildDate(2066.3.5); System.out.println(builder.date()); }}Copy the code
Output:
The 2066-3-5 2066 3 5Copy the code
Using different generators can make the original product behave a little differently.
Often in the real world, the work of generators is not so simple, but relatively complex (because their products are generally complex), and the maintenance of the original build is shifted to the maintenance of the generator.
5. Prototype Pattern
Specify the kinds of objects to create using a prototypical instance,and create new objects by copying this prototype. Specify what kind of objects to create with prototype instances, and create new objects by copying these prototypes.Copy the code
When to use:
- The program needs to start from an object and get several objects with the same state and can change its state independently.
- When the creation of an object needs to be independent of its construction and presentation.
- If an instance of a class can be defined as a prototype, it may be more convenient to copy the prototype from that instance than to create a new instance using the class’s constructor
Advantages:
- When it is more expensive to create a new instance of a class, copying an existing instance using the stereotype pattern can improve the efficiency of creating new instances.
- The state of the current object can be saved dynamically. At run time, a copy of the current object can be saved at any time using the object stream.
- New objects can be created at run time without having to create a series of classes and integration structures.
- Replicas of prototypes can be added and removed dynamically.
The prototype pattern requires an object to implement an interface that can “clone” itself, so that a new instance can be created by copying an instance object itself. In this way, when you create a new object from a prototype instance, you don’t need to care about the type of the instance itself. Once you implement the clone method, you can get the new object through this method instead of creating it through new.
The abstract prototype in this example does not use the method name clone() for reasons described below.
Prototype patterns in simple form:
// Concrete prototype
public class SimplePrototype implements Prototype.Cloneable {
int value;
/ / clone () implementation
@Override
public Object cloneSelf(a) {
SimplePrototype self = new SimplePrototype();
self.value = value;
return self;
}
/ / use
public static void main(String args[]){
SimplePrototype simplePrototype = new SimplePrototype();
simplePrototype.value = 500; SimplePrototype simplePrototypeClone = (SimplePrototype) simplePrototype.cloneSelf(); System.out.println(simplePrototypeClone.value); }}// Abstract prototype
interface Prototype{
Object cloneSelf(a);// Clone yourself
}
// Used by the client
class Client{
SimplePrototype prototype;
public Client(SimplePrototype prototype){
this.prototype = prototype;
}
public Object getPrototype(a){
returnprototype.cloneSelf(); }}Copy the code
The simple prototype pattern is that when clone() is implemented, a new instance is created and the member variables are assigned and returned.
Java native support
All classes in Java, directly or indirectly inherits from the Object class, the clone of the Object class () method: “protected native Object clone () throws CloneNotSupportedException; “, you can see that the permissions are protected, so only subclasses can access the method, but we can override the method in the subclass, raise the access to public, and return super.clone() from the method body.
We can see the Object method is may throw an exception, we must implement Cloneable interface, can use this method, otherwise will be thrown “Java. Lang. CloneNotSupportedException” exception. The Cloneable interface is empty and is implemented to let the JVM know that the object is replicable, otherwise a Clone () exception will occur. Here’s the demo code:
// Use Java built-in support
public class APITestUse {
public static void main(String args[]) throws CloneNotSupportedException{
MyObject myObject = new MyObject();
myObject.i = 500; MyObject myObjectClone = (MyObject) myObject.clone(); System.out.println(myObjectClone.i); }}// An object that can be copied
class MyObject implements Cloneable{
int i;
public Object clone(a) throws CloneNotSupportedException{
return super.clone(); }}// The result will output 500
Copy the code
When this method is called, the member variables are automatically copied. So if you need to use the prototype pattern, Java native support is good enough.
In addition to the above native support, there is a serialization in Java that only requires an object to implement the Serializable interface. In this way, we can write objects to a stream, save them to a file, or send them elsewhere over the network:
// Use Serializable to support cloning
public class SerializablePrototype implements Serializable{
private static final long serialVersionUID = 1L;
private int i;
private transient int notClone;// Members of the TRANSIENT keyword will not be serialized
public int getI(a) {
return i;
}
public void setI(int i) {
this.i = i;
}
public int getNotClone(a) {
return notClone;
}
public void setNotClone(int notClone) {
this.notClone = notClone;
}
public void writeToFile(String path) throws Exception{
FileOutputStream outStream = new FileOutputStream(path);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outStream);
objectOutputStream.writeObject(this);
outStream.close();
}
public SerializablePrototype ReadFromFile(String path) throws Exception{
File file = new File(path);
if(! file.exists()) file.createNewFile(); FileInputStream inStream =new FileInputStream(path);
ObjectInputStream objectOutputStream = new ObjectInputStream(inStream);
Object o= objectOutputStream.readObject();
inStream.close();
return (SerializablePrototype) o;
}
public static void main(String args[]) throws Exception{
String path = "D:/SerializablePrototype.instance";
SerializablePrototype prototype = new SerializablePrototype();
prototype.setI(123);
prototype.setNotClone(456);
prototype.writeToFile(path);
SerializablePrototype prototypeClone = new SerializablePrototype();
prototypeClone = prototype.ReadFromFile(path);
System.out.println(prototypeClone.getI() + ""+ prototypeClone.getNotClone()); }}// Output: 123 0
Copy the code
Let’s analyze the above example: This object has 3 member variables, one of which is declared as transient keyword, one is serialized ID, one is normal variable, in the main method, want to create the object, set the value, then write to the file, then read from the file, and finally output the variable of the read object, The normal variable can be output (serialization id can also be output, but the transient variable is 0, so that the variable is not stored in the file, because the variable declared by the keyword is ignored during serialization. Instead, it is automatically initialized to 0 when it is created later. (In Java, a class whose member variable is a primitive data type is automatically initialized to 0 if there is no initial value.)
Er… This is an introduction to patterns, and it’s a little too much to say, so that’s it for prototype patterns.
6. Chain of Responsibility Pattern
This avoids coupling between the sender and receiver of the request by giving many objects the opportunity to process the request. Connect the objects into a chain and pass the request along the chain until an object processes it.Copy the code
When to use
- There are many objects that can handle user requests, and you want your program to automatically determine which object to handle the user at run time.
- One submission request that wants multiple recipients without the user having to explicitly specify the recipient
- The program wants to dynamically specify a collection of objects that can handle user requests
advantages
- Low coupling
- Handlers can be added and removed dynamically or reassigned to their responsibilities
- The order of precedence between handlers can be changed dynamically. In general, a pure chain of responsibilities is passed to the first handler, if handled, the request processing ends, and if not, to the next handler.
For example, we have a mathematical formula that takes an integer input and returns the absolute value if it is less than 0, the second power if it is less than 10, and itself otherwise:
First we define an interface (handler) that describes their common behavior:
/ / handler
public interface Handler {
int handleRequest(int n);
void setNextHandler(Handler next);
}
Copy the code
Then there are the specific processors (3) :
// The first concrete handler handles those less than 0
public class Handler1 implements Handler {
private Handler next;
@Override
public int handleRequest(int n) {
if(n<0) return -n;
else{
if(next==null)
throw new NullPointerException("Next cannot be empty");
returnnext.handleRequest(n); }}@Override
public void setNextHandler(Handler next) {
this.next = next; }}Copy the code
// Second concrete handler, handle >=0 but less than 10
public class Handler2 implements Handler {
private Handler next;
@Override
public int handleRequest(int n) {
if(n<10) return n*n;
else{
if(next==null)
throw new NullPointerException("Next cannot be empty");
returnnext.handleRequest(n); }}@Override
public void setNextHandler(Handler next) {
this.next = next; }}Copy the code
// The third concrete handler handles those >=0 but less than 10
public class Handler3 implements Handler {
private Handler next;
@Override
public int handleRequest(int n) {
if(n<=Integer.MAX_VALUE) return n;
else{
if(next==null)
throw new NullPointerException("Next cannot be empty");
returnnext.handleRequest(n); }}@Override
public void setNextHandler(Handler next) {
this.next = next; }}Copy the code
Use:
public class TestUse {
public static void main(String args[]){
Handler h1,h2,h3;
h1 = new Handler1();
h2 = new Handler2();
h3 = new Handler3();
h1.setNextHandler(h2);
h2.setNextHandler(h3);
System.out.println(h1.handleRequest(-1));
System.out.println(h1.handleRequest(5));
System.out.println(h1.handleRequest(9999)); }}Copy the code
The order of the specific handlers in the responsibility chain cannot be reset, otherwise errors may be caused. However, in most cases, their positions can be changed at will, as in the example above, by resetting the conditions in if (independent and not interdependent).
When we write Java Web programs, we usually write some filters and configure them into web.xml, which is an exercise in the chain of responsibility pattern. With Log4j logging, the chain of responsibility pattern is also used when configuring levels.
When we use the chain of responsibility model, it is not necessary for one handler to terminate the delivery of the request, but we can continue the delivery of the request to the next specific handler if there are other requirements.
7. Command Pattern
Another Name: The Action, Transaction) Encapsulate a request as an object, and thereby allowing you to parameterize clients with different reauests,queue or log requests,and support undoable operations. Encapsulate a request as an object, allowing the user to parameterize the customer with different requests; Queue or log requests, and support undoable operations.Copy the code
When to use:
- The program needs to specify, align, and execute requests at different times.
- The program needs to provide an undo operation.
- Programs need to support macro operations.
advantages
- In command mode, the Invoker does not interact directly with the Receiver, and the Invoker does not contain a reference to the Receiver, thus eliminating coupling completely.
- The command mode meets the open-close rule. If a new concrete command and the recipient of that command are added, the caller can use the new command object without modifying the caller’s code. Conversely, if a new caller is added, the new caller can use the existing concrete command without modifying the existing concrete command and receiver.
- Because the requester’s request is encapsulated in a concrete command, the concrete command can be saved in the persistent medium and re-executed as needed. Therefore, logging can be done using command mode.
- Requester “requests” can be queued using command mode. Each request corresponds to a specific command, so the specific commands can be executed in a certain order.
An object has multiple operations, but we don’t want the caller (requester) to use them directly, so we add an additional object and let the caller use those operations through that object.
For example, we have a class that can create or delete files on disk (receiver), but we don’t want to make it available directly to others (requester), so we create commands for its various operations.
/ / the recipient
public class MakeFile {
// Create a new file
public void createFile(String name) throws IOException{
File file = new File(name);
file.createNewFile();
}
// Delete files
public boolean deleteFile(String name){
File file = new File(name);
if(file.exists()&&file.isFile()){
file.delete();
return true;
}
return false; }}Copy the code
Then there is the interface to perform the operation:
// Command interface
public interface Command {
void execute(String name) throws Exception;
}
Copy the code
We need to create specific commands, here are two, new and delete:
// Create a file command
public class CommandCreate implements Command {
MakeFile makeFile;
public CommandCreate(MakeFile makeFile) {
this.makeFile = makeFile;
}
@Override
public void execute(String name) throws Exception { makeFile.createFile(name); }}Copy the code
// Delete file command
public class CommandDelete implements Command{
MakeFile makeFile;
public CommandDelete(MakeFile makeFile) {
this.makeFile = makeFile;
}
@Override
public void execute(String name) throws Exception { makeFile.deleteFile(name); }}Copy the code
Thus, we can use it as follows:
Public class TestUse {public static void main(String args[]) throws Exception{// Receiver MakeFile MakeFile = new MakeFile(); CommandCreate create = new CommandCreate(makeFile); CommandDelete delete = new CommandDelete(makeFile); Client = new Client(); Client.setcommand (create).executecommand ("d://test1.txt");
client.setCommand(create).executeCommand("d://test2.txt");
client.setCommand(delete).executeCommand("d://test2.txt"); }}// There will be one on drive D after executiontest1. TXT files,test2. TXT this page was created, but was deleted.Copy the code
This is just a simple implementation, such as CommandCreate operations, if we need undo, then we need to add the undo() method to the command interface and implement it in the specific command (save the operation on the stack, undo the stack and undo).
The command mode should not be abused. For example, using this mode will create many more objects (commands).
In command mode, there is also a specific command called macro command, which contains some references to other specific commands. Executing a macro command can execute the reference command contained in the macro command. After the concept is clear, it is also easy to implement:
For example, there are Chinese output commands, English output commands, and macro commands. The macro command contains references to the other two commands (which can be saved using lists). If the macro command is executed, the macro command executes the other commands that it contains references once (iterating through the list of commands and executing it).
8. Interpreter Patterm
Given a language,define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language. Given a language, define a representation of its grammar, and define an interpreter that uses that representation to interpret sentences in the language.Copy the code
When to use
- The interpreter pattern is used when there is a simple language that needs to explain execution, and each rule of that language can be represented as a class.
advantages
- Each syntactic rule is represented as a class to facilitate the implementation of simple languages.
- Because classes represent the rules of a grammar, it is easier to change or extend the behavior of a language.
- By adding new methods to the class structure, you can add new behavior along with interpretation.
The concept is simple. On some issues, we may wish to define simple language to describe them, and then we can explain it ourselves.
The interpreter pattern generally includes four roles:
- Abstract expression: This role is an interface that defines abstract explain operations.
- Terminator expressions: A class that implements an interface to abstract expressions.
- Nonterminal expressions: Also a class that implements abstract expressions.
- Context: Contains global information outside of the interpreter.
Designing a program using this pattern typically involves three steps:
- Parse action markers in statements.
- Specify the tag as an action.
- Perform the action.
This pattern is usually applied to specific problems, and the use of this pattern generally requires a basic knowledge of formal languages. The JS kernel is a powerful interpreter.
Simple interpreter mode, we need to interpret the expression of the information can be; Further down the line, we need to translate the content of the expression into a part of our program to execute it.
No examples are provided. Just understand the concept. Learn more when you need to. If you do need examples, please mention them in the reply and I will update the post and add some.
Iterator pattern
Aliases: Cursors (Another Name: Cursors) provide a way to access the elements of an aggregate object sequentially without exposing the internal details of the object.Copy the code
When to use
-
When users are given access to a collection of summarized objects without exposing the implementation of the collection
-
Advantages when providing a uniform traversal interface for different collections
-
Users use iterators to access objects in a collection without knowing the implementation of the collection
-
Multiple iterators can be used simultaneously to traverse a collection
Iterators provided by containers can iterate over their own iterators at high speed, while traversal performance using their own mechanisms (such as get(I) traversal in LinkedList) may not be good.
In fact, the example given in the factory method pattern is sufficient to explain the use of this pattern, to see the code implementation, please refer to the factory method pattern example.
The main roles are set, concrete set, iterator, concrete iterator.
Iterators are actually quite simple, so let’s continue with the example from the factory method pattern and refine it:
Slightly enhanced collection interface:
// Set interface
public interface MyList<T> {
MyIterator<T> iterator(a); // Return a traverser
boolean add(T t); // Add elements to the list
T get(int index); // Get the element
T remove(a); // Remove the last element
boolean remove(T element); // Deletes the specified element
T remove(int index); // Remove the element at the specified location
boolean set(int index,T element); // Change the value of the specified position
int size(a);
}
Copy the code
A collection of array implementations whose capacity can automatically grow:
public class MyArrayList<T> implements MyList<T>{
private int size; // The number of stored elements is initialized to 0 by default
private Object[] defaultList; // Use arrays to store elements
private static final int defaultLength = 10;// The default length
public MyArrayList(a){ // Default constructor
defaultList = new Object[defaultLength];
}
@Override
public MyIterator<T> iterator(a) {
return new Iterator();
}
// The size grows automatically
private void ensureCapacity(int capacity){
int nowLength = defaultList.length;
if(capacity >= nowLength){
nowLength = nowLength + (nowLength>>1);
if(nowLength<0)/ / overflownowLength = Integer.MAX_VALUE; defaultList = Arrays.copyOf(defaultList, nowLength); }}// Add elements
@Override
public boolean add(T t) {
ensureCapacity(size+1);
defaultList[size++] = t;
return true;
}
// Get the element
@SuppressWarnings("unchecked")
@Override
public T get(int index) {
if(index<0 || index>=size) return null;
return (T) defaultList[index];
}
@Override
public T remove(a) {
return remove(size-1);
}
@SuppressWarnings("unchecked")
@Override
public T remove(int index) {
if(index<0||index>=size) return null;
T element = (T) defaultList[index];
if(index ! = size-1)
System.arraycopy(defaultList, index+1, defaultList, index,size-index-1);
size--;
return element;
}
@Override
public boolean remove(T element) {
if(element==null) {for(int i = 0; i<size; i++)if(defaultList[i]==null){
System.arraycopy(defaultList, i+1, defaultList, i,size-i-1);
size--;
return true; }}else{
for(int i = 0; i<size; i++)if(defaultList[i].equals(element)){
System.arraycopy(defaultList, i+1, defaultList, i,size-i-1);
size--;
return true; }}return false;
}
@Override
public boolean set(int index,T element) {
if(index<0||index>=size) return false;
defaultList[index] = element;
return true;
}
@Override
public int size(a) {
return size;
}
/ / the iterator
private class Iterator implements MyIterator<T>{
private int next;
@Override
public boolean hasNext(a) {
return next<size;
}
@SuppressWarnings("unchecked")
@Override
public T next(a) {
return (T) defaultList[next++];
}
@Override
public T remove(a) {
// TODO Auto-generated method stub
return null; }}}Copy the code
A collection of linked list implementations:
public class MyLinkedList<T> implements MyList<T>{
private int size; // The number of stored elements is initialized to 0 by default
private Node<T> first; // The first node is initialized to null by default
@Override
public MyIterator<T> iterator(a) {
return new Iterator();
}
@Override
public boolean add(T t) {
if(size==0){
first = new Node<T>(t,null);
size++;
return true;
}
Node<T> node = first;
while(node.next! =null)
node = node.next;
node.next = new Node<T>(t,null);
size++;
return true;
}
@Override
public T get(int index) {
Node<T> node = first;
while(--index>=0)
node = node.next;
return node.data;
}
@Override
public T remove(a) {
return remove(size-1);
}
@Override
public T remove(int index) {
if(index<0||index>=size) return null;
Node<T> node = first;
while(--index>0)
node = node.next;
T element = node.next.data;
node.next = node.next.next;
size--;
return element;
}
@Override
public boolean remove(T element) {
if(element == null) {if(first.data==null){
first = first.next;
size--;
return true;
}
Node<T> node = first;
do{
if(node.next.data==null){
node.next = node.next.next;
size--;
return true;
}
node = node.next;
}
while(node.next! =null);
}
else{
if(first.data.equals(element)){
first = first.next;
size--;
return true;
}
Node<T> node = first;
do{
if(node.next.data.equals(element)){
node.next = node.next.next;
size--;
return true;
}
node = node.next;
}
while(node.next! =null);
}
return false;
}
@Override
public boolean set(int index, T element) {
if(index<0||index>=size) return false;
Node<T> node = first;
while(--index>0)
node = node.next;
node.data = element;
return true;
}
@Override
public int size(a) {
return size;
}
// List nodes
private static class Node<T>{
T data;
Node<T> next;
Node(T data,Node<T> next){
this.data = data;
this.next = next; }}/ / traverse
private class Iterator implements MyIterator<T>{
private Node<T> next; // Next node
Iterator(){
next = first;
}
@Override
public boolean hasNext(a) {
returnnext! =null;
}
@Override
public T next(a) {
T data = next.data;
next = next.next;
return data;
}
@Override
public T remove(a) {
// TODO Auto-generated method stub
return null; }}}Copy the code
Iterator interface:
public interface MyIterator<T> {
boolean hasNext(a); // If there is a next element
T next(a); // Get the next element
T remove(a);
}
Copy the code
A concrete iterator is an iterator inner class in a concrete implementation of the collection. Here’s how to use it:
public class TestUse {
public static void main(String args[]){
// Define the two structures respectively
MyList<String> array = new MyArrayList<String>();
MyList<String> link = new MyLinkedList<String>();
// Add data
for(int i = 1; i <15; i++){
array.add(i+"");
link.add(i+"");
}
// Array manipulation
System.out.println(array.remove());
System.out.println(array.get(5));
System.out.println(array.remove(5));
System.out.println(array.get(5));
System.out.println(array.remove("Seven"));
System.out.println(array.set(0."00"));
// Use iterators
MyIterator<String> ai = array.iterator();
while(ai.hasNext())
System.out.print(ai.next()+"");
System.out.println();
System.out.println(link.remove());
System.out.println(link.get(5));
System.out.println(link.remove(5));
System.out.println(link.get(5));
System.out.println(link.remove("Seven"));
System.out.println(link.set(0."00"));
// Use iterators
MyIterator<String> li = link.iterator();
while(li.hasNext())
System.out.print(li.next()+"");
System.out.println();
System.out.println("a size=" + array.size());
System.out.println("l size="+ link.size()); }}Copy the code
Output:
14 June 6 Julytrue
true
00 2 3 4 5 8 9 10 11 12 13
14
6
6
7
true
true
00 2 3 4 5 8 9 10 11 12 13
a size=11
l size=11
Copy the code
The iterator here is a typical implementation of the iterator pattern (except that the iterator here does not implement the remove() method, and the remove() method for finding collections is also easy to implement).
10. Mediator Pattern
A mediation object encapsulates a set of object interactions. The mediator removes the need for objects to be explicitly referenced to each other, allowing them to be loosely coupled and independently change their previous interactions.Copy the code
A direct correlation between two classes is fine, but if you don’t want the two classes to interact directly, you need to use the mediator pattern.
For example, there are two classes, both for persistence, one for writing data to a file and one for writing data to a database. It is uncertain which of them receives the data first, but to ensure that once one receives the data, the other must persist it as well. If we associate the two classes directly, it is possible to call each other, but it is not conducive to later extension or maintenance (for example, adding another persistent component may require modification of the existing component). In this case, it is much easier to add a mediator to coordinate them:
Interface for data persistence:
// Colleagues (interface)
public interface IPersistent {
void getData(Object data);
void getData(Object data,Midiator midiator);
void saveData(a);
}
Copy the code
Components that persist to files and to databases, respectively:
// Specific colleagues
public class PersistentFile implements IPersistent{
private Object data;
@Override
public void getData(Object data, Midiator midiator) {
getData(data);
midiator.notifyOther(this, data);
}
@Override
public void saveData(a) {
System.out.println(data + "Saved to file");
}
@Override
public void getData(Object data) {
this.data = data; saveData(); }}Copy the code
// Specific colleagues
public class PersistentDB implements IPersistent{
private Object data;
@Override
public void getData(Object data, Midiator midiator) {
getData(data);
midiator.notifyOther(this, data);
}
@Override
public void saveData(a) {
System.out.println(data + "Saved to database");
}
@Override
public void getData(Object data) {
this.data = data; saveData(); }}Copy the code
Broker:
// Specific intermediaries
public class Midiator {
PersistentDB persistentDB;// We can use the List here to store all colleagues
PersistentFile persistentFile;
public Midiator setPersistentDB(PersistentDB persistentDB) {
this.persistentDB = persistentDB;
return this;
}
public Midiator setPersistentFile(PersistentFile persistentFile) {
this.persistentFile = persistentFile;
return this;
}
public void notifyOther(IPersistent persistent,Object data){
if(persistent instanceof PersistentDB)// If all colleagues are in the List, we can iterate here
persistentFile.getData(data);
if(persistent instanceofPersistentFile) persistentDB.getData(data); }}Copy the code
Use:
public class TestUse {
public static void main(String args[]){
Object data = "Data";
PersistentDB persistentDB = new PersistentDB();
PersistentFile persistentFile = new PersistentFile();
Midiator midiator = newMidiator(); midiator.setPersistentDB(persistentDB).setPersistentFile(persistentFile); persistentDB.getData(data, midiator); persistentFile.getData(data, midiator); }}// Output (newline omitted) : Data saved to database Data saved to file Data saved to file Data saved to database
Copy the code
For example, if there are many persistent components, you can use a List in the mediator to store their references, which is added at set time. When notifying other colleagues, iterate through the List, except for the parameter itself, the other colleagues in turn, can be realized.
The mediator eliminates direct connections between colleagues.
11. Memento Pattern
Aliases: tags (Another Name: The standing committee of the National People's Congress widened the definition of traditional Chinese law by making illegal substitution for traditional Chinese law Without encapsulating,captrue and externalizing an object 'orifianl state so that the object can be restored to this state later. It is possible to restore an object to its previously saved state by capturing the internal state of an object and saving the state outside of the object without breaking encapsulation.Copy the code
When to use:
- All or part of the state of an object at a point in time must be saved so that the object’s previous state can be restored if needed.
- An object does not want to give other objects access to its IDE internal state by providing public access to methods such as getXXX().
Advantages:
- The memo mode can use the memo to save all the internal state of the original, so that there is a very “intimate” object can access the data in the memo.
- The memo pattern emphasizes the principle of single responsibility for class design, separating delineation and preservation of state.
The memo mode, also called the Snapshot Pattern or Token mode, is the behavior mode of objects. A memo object is an object that stores a snapshot of the internal state of another object.
There are three roles in memo mode:
- Memento role: Stores the civil war status of the Originator object. Memos can determine how many Originator objects’ internal state to store based on the Originator object’s judgment. Memos protect their contents from being read by any object other than the Originator object.
- Originator role: Creates a memo object that contains the current internal state. Use a memo object to store its internal state.
- Caretaker role: Saves the Caretaker. Do not check the contents of the memo object.
// Simple memo mode
public class SimpleMemento {
public static void main(String[] args) throws Exception {
Originator originator = new Originator(); // The originator, the object to be saved, also creates the information to be saved
Caretaker caretaker = new Caretaker(); // Help save the object
originator.setState("stateOne"); // Set the state
caretaker.saveMemento(originator.createMemento()); // Save the state
originator.setState("stateTwo"); // Change the status
originator.recoverMemento(caretaker.recoverMemento()); // Restore the status}}/ / the originator
class Originator {
private String state;
public Memento createMemento(a){
return new Memento(state);
}
public void recoverMemento(Memento memento){
this.state = memento.getState();
}
public String getState(a) {
return state;
}
public void setState(String state) {
this.state = state; }}/ / memo
class Memento {
private String state;
public Memento(String state){
this.state = state;
}
public String getState(a) {
return state;
}
public void setState(String state) {
this.state = state; }}/ / head
class Caretaker {
private Memento memento;
public Memento recoverMemento(a) throws Exception{
if(memento==null)
throw new Exception("No saved state");
return this.memento;// Restore the status
}
public void saveMemento(Memento memento){
this.memento = memento;// Save the state}}Copy the code
The memo role provides an interface to any object, and the state stored inside the memo role is exposed to all objects, thus breaking encapsulation.
The memo role is required by the definition to remain fully encapsulated. Best case scenario: the memo role should only expose the interface that manipulates the internal storage properties to the Memo initiator role.
If the case, we put the memo by the originator of the private inner class, it can only be visited the sponsor, this just accord with the requirement of memorandum mode, but our head is the need to store reference of the memo, so we provide a common interface, he is empty, we use the memo to achieve it, Mainly is to use the type information, the specific implementation is as follows:
// Memo mode
public class BlackMemento {
public static void main(String[] args) {
BlankOriginator originator = new BlankOriginator(); / / the originator
BlackCaretaker caretaker = new BlackCaretaker(); / / head
originator.setState("stateOne"); // Set the state
caretaker.saveMemento(originator.createMemento()); // Save the information
originator.setState("stateTwo"); // Change the status
originator.recoverMemento(caretaker.recoverMemento());// Restore the status}}interface MementoIF {}
/ / the originator
class BlankOriginator {
private String state;
public String getState(a) {
return state;
}
public void setState(String state) {
this.state = state;
}
public MementoIF createMemento(a){
return new Memento(state);
}
public void recoverMemento(MementoIF memento){
this.setState(((Memento)memento).getState());
}
// The internal class implements the memo role
private class Memento implements MementoIF{
private String state;
private Memento(String state){
this.state = state;
}
private String getState(a) {
returnstate; }}}/ / head
class BlackCaretaker {
private MementoIF memento;
public MementoIF recoverMemento(a){
return memento;
}
public void saveMemento(MementoIF memento){
this.memento = memento; }}Copy the code
The above two examples demonstrate that a state is stored (not a member, but only the most recent state), i.e. a checkpoint, but in practice, states are stored more than once. We can change the variables above to a stack (or queue, depending on the requirements). E.g. BlackCaretaker: private MementoIF memento; Change LinkedList< MementoIF > mementos implementation, save when unstack (join), restore when unstack (join). The implementation is clearly described, so I won’t post the code (the article is too long anyway).
For the above example, if the sponsor and the director are not concerned that they must be independent, we can merge them together and the implementation will be simpler and the code will be simpler:
// A personal history memo
public class MementoSelf {
public static void main(String[] args) {
OriginatorCaretaker originatorCaretaker = new OriginatorCaretaker();// The sponsor is also the person in charge
originatorCaretaker.changeState("stateOne"); // Change the state
IMemento memento = originatorCaretaker.createMemento(); // Save the state
originatorCaretaker.changeState("stateTwo"); // Change the state
originatorCaretaker.recoverMemento(memento); // Restore the status}}interface IMemento {}
//
class OriginatorCaretaker {
public String state;
public void changeState(String state){
this.state = state;
}
// Create a snapshot
public Memento createMemento(a){
return new Memento(this);
}
// Restore the status
public void recoverMemento(IMemento memento){
Memento m = (Memento)memento;
changeState(m.state);
}
// The inner class implements the memo
private class Memento implements IMemento{
private String state;
private Memento(OriginatorCaretaker originatorCaretaker){
this.state = originatorCaretaker.state; }}}Copy the code
The above example demonstrates saving only one checkpoint. Here’s another practical example:
We have a program for the user to edit the text, the user to make changes, can save the text, save the changes, can be successively restored to one of the states before saving, if the recovery of the user did not modify, can also cancel the recovery (redo), the following shows the whole program.
The writing demo may be a bit long to keep the program relatively complete:
// Text editor
public class TextEditor {
public static void main(String[] args) {
// Use the text editor
MyTextEditor editor = new MyTextEditor("Here is the initial text, possibly the value read from the file.");
System.out.println("Start modifying text:");
editor.append("Add text 1");
editor.delWords(); // Delete the last one
// editor.delWords(2); // There is no problem with removing the last two methods. To avoid too much console output, cancel these two changes
/ / editor. DelWords (1, 5); // Delete the first five
System.out.println("Begin to recover:");
for(int i=0; i<10; i++) editor.recoverMemento();// There is no error in restoring more than the actual number of changes, only setting the text to o initialization state
System.out.println("Start redo:");
for(int i=0; i<10; i++) editor.redo();// There is no error in redoing more than the actual number of restores, only setting the text to the last state
System.out.println("Recover again:");
for(int i=0; i<10; i++) editor.recoverMemento();// There is no error in restoring more than the actual number of changes, only setting the text to o initialization state
System.out.println("Redo again:");
for(int i=0; i<10; i++) editor.redo();// There is no error in redoing more than the actual number of restores, only setting the text to the last state
System.out.println("Recover again:");
for(int i=0; i<10; i++) editor.recoverMemento();// There is no error in restoring more than the actual number of changes, only setting the text to o initialization state
editor.append("Add text 2");
System.out.println("Redo again:");
for(int i=0; i<10; i++) editor.redo();// There is no error in redoing more than the actual number of restores, only setting the text to the last state}}interface IMemento {}
//
class MyTextEditor {
public StringBuffer text;
private LinkedList<IMemento> mementos; // Save the snapshot
private LinkedList<IMemento> undos; // Save the undo operation
public MyTextEditor(a){
this("");
}
public MyTextEditor(String defaultStr){
text = new StringBuffer(defaultStr);
mementos = new LinkedList<IMemento>();
undos = new LinkedList<IMemento>();
print();
}
public void clearHistory(a){
mementos.clear();
undos.clear();
}
public void append(String appendStr){
if(appendStr==null||appendStr.length()==0) return;
createMemento();
text.append(appendStr);
print();
undos.clear();
}
// Delete the last one
public void delWords(a){
delWords(1);
}
// Delete the last n entries
public void delWords(int n){
if(n<1||n>text.length()) return;
delWords(text.length()-n+1,text.length());
}
// Delete the middle start to end character, the first character is the first (instead of 0).
public void delWords(int start,int end){
if(start<1 || end>text.length()+1) return;
createMemento();
text = text.delete(start-1, end);
print();
}
public void reset(String text){
this.text = new StringBuffer(text);
}
// New snapshot
public void createMemento(a){
mementos.push(new Memento(this));
}
// Restore the status
public boolean recoverMemento(a){
Memento memento = (Memento) mementos.poll();
if(memento==null) return false;
undos.push(new Memento(this));
reset(memento.state);
print();
return true;
}
// Redo,redo operations can also be restored!
public boolean redo(a){
Memento memento = (Memento) undos.poll();
if(memento==null) return false;
createMemento();
reset(memento.state);
print();
return true;
}
// The inner class implements the memo
private class Memento implements IMemento{
private String state;
private Memento(MyTextEditor editor){
this.state = editor.text.toString(); }}void print(a){
System.out.println("Current text:"+ text); }}Copy the code
Console output:
Current text: This is the initial text, which may be the value read from the file. Start modifying text: Current text: Here is the initial text, which may be the value read from the file. Add text 1 Current text: This is the initial text, which may be the value read from the file. Add text start Restore: Current text: Here is the initial text, which may be the value read in the file. Add text 1 Current text: This is the initial text, which may be the value read from the file. Start redo: Current text: Here is the initial text, which may be the value read from the file. Add text 1 Current text: This is the initial text, which may be the value read from the file. Add text again Restore: Current text: Here is the initial text, which may be the value read in the file. Add text 1 Current text: This is the initial text, which may be the value read from the file. Redo: Current text: Here is the initial text, which may be the value read from the file. Add text 1 Current text: This is the initial text, which may be the value read from the file. Add text again Restore: Current text: Here is the initial text, which may be the value read in the file. Add text 1 Current text: This is the initial text, which may be the value read from the file. Current text: This is the initial text, which may be the value read from the file. Add text 2 to redo again:Copy the code
As you can see, the functionality is correct, and the final redo is invalid because the changes occurred after the restore (this is the strategy for all the editors we use so far). Multiple recoveries and reworks are fine.
This example is a typical example of the memo pattern.
12. Observer Pattern
Another Name: Publish/Subscribe (Another Name: Publish/Subscribe) Defines a one-to-many dependency between objects in which all dependent objects are notified and automatically updated when an object's state changes.Copy the code
When to use
- When one object needs to notify other objects when its data is updated, but you do not want a tight coupling with the notified object
For example, if we have a weather service (theme) and multiple clients (observer) that use it, including android and iPhone app services (Observer), we can use this pattern.
We need a structure to store weather information (note that get and set methods are omitted!). :
// Weather message entities
public class WeatherInfo {
private long time;
private String weather;
public WeatherInfo(long time,String weather){
this.time = time;
this.weather = weather;
}
@Override
public boolean equals(Object obj) {
WeatherInfo info = (WeatherInfo) obj;
return info.time==this.time&&info.weather.equals(this.weather); }}Copy the code
We then define the interface (topic) of the weather service to indicate what it should do:
/ / theme
public interface IWeatherService {
void addClient(Client client); // Add an observer
boolean deleteClient(Client client);// Delete the observer
void notifyClients(a); / / notice
void updateWeather(WeatherInfo info);// Theme content updated
}
Copy the code
Then the client interface description:
/ / observer
public interface Client {
void getWeather(WeatherInfo info);
}
Copy the code
Then implement the specific weather service, again using the singleton pattern:
// Specific topics
public enum WeatherService implements IWeatherService{
instance;
private LinkedList<WeatherInfo> weatherInfos = new LinkedList<WeatherInfo>();
private LinkedHashSet<Client> clients = new LinkedHashSet<Client>(); // Store the observer
// Add an observer
@Override
public void addClient(Client client) {
clients.add(client);
}
// Delete the observer
@Override
public boolean deleteClient(Client client) {
return clients.remove(client);
}
// Notify the observer
@Override
public void notifyClients(a) {
Iterator<Client> iterator = clients.iterator();
while(iterator.hasNext()){ iterator.next().getWeather(weatherInfos.peekFirst()); }}// Weather update
@Override
public void updateWeather(WeatherInfo info) {
if(weatherInfos.size()>0)
if(weatherInfos.peekFirst().equals(info)) return;
weatherInfos.push(info);
if(clients.size()==0) return; notifyClients(); }}Copy the code
Finally, there are specific clients (observers, two of which are given here) :
public class ClientAndroidServer implements Client {
private static String name = "Android Services";
private WeatherInfo info;
@Override
public void getWeather(WeatherInfo info) {
this.info = info;
dealMsg();
}
private void dealMsg(a){
System.out.println(name + "Received weather update: time="+info.getTime()+"msg="+info.getWeather()+". Start pushing notifications right away..."); }}Copy the code
public class ClientIphoneServer implements Client {
private static String name = "Apple Services";
private WeatherInfo info;
@Override
public void getWeather(WeatherInfo info) {
this.info = info;
dealMsg();
}
private void dealMsg(a){
System.out.println(name + "Received weather update: time="+info.getTime()+"msg="+info.getWeather()+". Start pushing notifications right away..."); }}Copy the code
Ok, now you can use it directly:
public class TestUse {
public static void main(String args[]){
// Create a theme
WeatherService service = WeatherService.instance;
// Add an observer
service.addClient(new ClientAndroidServer());
service.addClient(new ClientIphoneServer());
// Update the theme
service.updateWeather(new WeatherInfo(System.currentTimeMillis(), "Cloudy"));
service.updateWeather(new WeatherInfo(System.currentTimeMillis()+1000*60*60*24."Cloudy to clear"));
service.updateWeather(new WeatherInfo(System.currentTimeMillis()+1000*60*60*24*2."Fine")); }}Copy the code
After running, the console has the following output:
Android service received latest weather: time=1461246047007msg= cloudy Start pushing notifications right away... Apple service received the latest weather: time=1461246047007msg= cloudy. Start pushing notifications right away... Time =1461332447007msg= Cloudy to clear. Start pushing notifications right away... Apple service received the latest weather: time=1461332447007msg= cloudy to clear. Start pushing notifications right away... Android service received the latest weather: time=1461418847007msg= sunny. Start pushing notifications right away... Apple service received the latest weather: time=1461418847007msg= sunny. Start pushing notifications right away...Copy the code
As you can see, the observer pattern is one-to-many. In this case, updates are pushed to the client entirely.
In the observer mode, there is a difference between push and pull, in this case, push.
In push mode, the updated content of a theme is directly pushed to the client. In pull mode, after the updated data of a theme is pushed to the client, a notification is first pushed and corresponding methods are provided for the client to pull the data.
On case, if the weather service to update every half hour (half and hour push messages), and a client, do not need special real-time weather news, only take the news of the hour, so we can use the way of, data update, a sign to the client push, client own on-demand data (the weather) need to provide such a service interface. This is pulling.
Support for the observer pattern is also provided in the java.util package because it is widely used in Java programming. There is an Observable class (equivalent to the specific topic here) and an Observer interface (equivalent to the topic interface here) :
public interface Observer {
void update(Observable o, Object arg);
}
Copy the code
public class Observable {
private boolean changed = false;
private Vector<Observer> obs;
public Observable(a) { obs = newVector<>(); }public synchronized void addObserver(Observer o) {
if (o == null) throw new NullPointerException();
if (!obs.contains(o)) { obs.addElement(o); }
}
public synchronized void deleteObserver(Observer o) { obs.removeElement(o); }
public void notifyObservers(a) { notifyObservers(null); }
public void notifyObservers(Object arg) {
Object[] arrLocal;
synchronized (this) {
if(! changed)return;
arrLocal = obs.toArray();
clearChanged();
}
for (int i = arrLocal.length-1; i>=0; i--) ((Observer)arrLocal[i]).update(this, arg);
}
public synchronized void deleteObservers(a) { obs.removeAllElements(); }
protected synchronized void setChanged(a) { changed = true; }
protected synchronized void clearChanged(a) { changed = false; }
public synchronized boolean hasChanged(a) { return changed; }
public synchronized int countObservers(a) { returnobs.size(); }}Copy the code
In fact, the above example is similar, if you need to use this aspect, you can also directly use the Java API. However, we can see that we still use Vector(outdated), which is not recommended. We can implement observer mode ourselves, or if we are multi-threaded, we can implement synchronization ourselves.
13. State Pattern
Allow an object to alter its behavior when its internal state changes.The object will appear to change its class. Allows an object to change its behavior when its internal state changes, and the object appears to modify its class.Copy the code
When to use:
- An object’s behavior depends on its state, and it must change its behavior based on state at run time.
- A large number of conditional branching statements are written to determine the behavior of an operation, and these conditions represent just one state of the object.
Advantages:
- Using a class to encapsulate one state of an object makes it easy to add new states
- In state mode, there is no need to have a large number of conditional statements in the Context. The state presented by Context instances becomes clearer and easier to understand.
- Using state mode makes it easy for user programs to switch the state of Context instances.
- Using state patterns does not allow instances in the Context to have internal state inconsistencies.
- When a state object has no instance variable, instances of the Context can share a state object.
In one sentence, the state pattern wraps the behavior of the studied objects in different state objects, each of which belongs to a subclass of an abstract state class. The intent of the state pattern is for an object to change its behavior when its internal state changes.
Maybe I’ve been thinking about databases a lot these days, so when I think of examples, I think of this aspect… However, it is also a good way to compare design patterns, and this example is relevant in this respect.
Imagine that we have a program that wants to store data in different ways, depending on the size of the data (in this case, String). If the data is small, we save it to Redis (cached database), if the database is not too small and not too big, we save it to mysql, and if the data is very large, we write it directly to a file. The size of the data is a kind of state, which is good for using state patterns:
Environment:
// Context
public class SaveDataController {
private ISaveData saveData;
public void save(String data){
// For demonstration purposes, the big data here is also very small
if(data.length()<1<<2)
saveData = SaveSmallData.instance;
else if(data.length()<1<<4)
saveData = SaveMiddleData.instance;
elsesaveData = SaveBigData.instance; saveData.save(data); }}Copy the code
Abstract state:
// Abstract state
public interface ISaveData {
void save(Object data);
}
Copy the code
Concrete state Each concrete state uses the singleton pattern) :
// Specific state
public enum SaveSmallData implements ISaveData{
instance;
@Override
public void save(Object data) {
System.out.println("Save to Redis:"+ data); }}Copy the code
// Specific state
public enum SaveMiddleData implements ISaveData{
instance;
@Override
public void save(Object data) {
System.out.println("Save to Mysql:"+ data); }}Copy the code
// Specific state
public enum SaveBigData implements ISaveData{
instance;
@Override
public void save(Object data) {
System.out.println("Save to file :"+ data); }}Copy the code
Use:
public class TestUse {
public static void main(String args[]){
String smallData = "Small data";
String middleData = "Data between small data and big data.";
String bifgData = "Let's just assume that this is a big, big, big number.";
SaveDataController saveDataController = newSaveDataController(); saveDataController.save(smallData); saveDataController.save(middleData); saveDataController.save(bifgData); }}Copy the code
Output:
Save to Redis: save small data to Mysql: Save data between small data and big data to files: let's assume this is a big, big, big dataCopy the code
As you can see, we use the same method for the same object for all three types of data, but the behavior is different because their states are different.
The state changes in the above example are automatic, or you can add a setState() method that manually switches the state and does not automatically determine the state in the body of the executing method. But automatic judgment, more intelligent, and manual switching state, more controllable.
14. Strategy Pattern
Define a set of algorithms, encapsulate them one by one, and make them interchangeable. This mode allows the algorithm to change independently of other clients.Copy the code
The policy pattern is a wrapper around the algorithm, which separates the responsibility of using the algorithm from the algorithm itself and delegates it to different object management. Policy patterns typically wrap a set of algorithms into a set of policy classes as a subclass of an abstract policy class. In a word: “Prepare a set of algorithms and encapsulate each algorithm so that they are interchangeable”. The following illustrates the structure of an instance of the policy pattern with a schematic implementation.
There are three roles in the policy pattern:
- Strategy: An interface that defines several algorithms (abstract methods).
- ConcreteStrategy: the implementation of a strategy.
- Context: A class that depends on the policy interface.
The focus of the strategy pattern is not how to implement the algorithm, but how to organize and call the algorithm, so that the program structure is more flexible, has better maintainability and scalability.
A great feature of the strategy pattern is the equality of each strategy algorithm. For a specific set of strategy algorithms, everyone has the same status, and because of this equality, algorithms can be replaced with each other. All the strategy algorithms are independent of each other in implementation, there is no dependence on each other. So this series of policy algorithms can be described as follows: Policy algorithms are different implementations of the same behavior.
At run time, the policy pattern can use only one specific policy implementation object at a time, and while it can dynamically switch between different policy implementations, only one can be used at a time.
It is common to see that all concrete policy classes have some common behavior. At this point, these common behaviors should be placed in the common abstract policy role Strategy class. Of course, the abstract policy role must be implemented using Java abstract classes, not interfaces. This is also a typical standard practice for centralizing code up the inheritance hierarchy.
Last time we used state mode to store data in different states. Here, we use policy mode to select the way data is stored by different policies.
The first is the abstract data retention class (policy) :
/ / strategy
public interface ISaveData {
void save(Object data);
}
Copy the code
Then there are the concrete data preservation classes, three (concrete policies) :
public class SaveToRedis implements ISaveData {
@Override
public void save(Object data) {
System.out.println("Data:" + data + "Save to Redis"); }}Copy the code
// Specific strategies
public class SaveToFile implements ISaveData {
@Override
public void save(Object data) {
System.out.println("Data:" + data + "Save to file"); }}Copy the code
Public class SaveToMysql implements ISaveData {@override public void save(Object data) {system.out.println (implements ISaveData;"Data:" + data + "Save to Mysql"); }}Copy the code
Finally, the client Context:
/ / environment
public class SaveClient {
private ISaveData saveData;
public SaveClient(ISaveData saveData){
this.saveData = saveData;
}
public void setSaveData(ISaveData saveData){
this.saveData = saveData;
}
public void save(Object data){ saveData.save(data); }}Copy the code
Use:
public class TestUse {
public static void main(String args[]){
Object data = "Data";
ISaveData saveData = new SaveToRedis();
SaveClient client = new SaveClient(saveData);
client.save(data);
client.setSaveData(newSaveToFile()); client.save(data); }}Copy the code
Here the data is saved according to the policy set at the time of use.
Use the policy pattern to avoid multiple conditional (if-else) statements. Multiple conditional statement is not easy to maintain, it takes which algorithm or take which behavior of the logic and algorithm or behavior of the logic mixed together, all listed in a multiple conditional statement, than the use of inherited methods but also primitive and backward.
The client must know all the policy classes and decide which one to use. This means that the client must understand the differences between these algorithms in order to choose the right algorithm class at the right time. In other words, the policy pattern only works if the client knows the algorithm or behavior. Because the policy pattern encapsulates each specific policy implementation as a separate class, the number of objects can be significant if there are many alternative policies.
Template Method Pattern Template Method Pattern
Define the skeleton of an algorithm in an operation,deferring some steps to subclasses.Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure. Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. The template method allows subclasses to redefine specific steps of an algorithm without changing the structure of that algorithm.Copy the code
When to use:
- Designers need to give a fixed set of steps for an algorithm and leave the implementation of certain steps to subclasses.
- The code needs to be refactored to extract the common behavior of each subclass into a common parent class to avoid code duplication.
Advantages:
- You can define the template method in the abstract bar to give mature algorithm steps, while not limiting the details of the steps, the specific template implementation algorithm details will not change the skeleton of the whole algorithm.
- In the abstract template pattern, some steps can be hooked through hook methods, and the concrete template can select some steps in the algorithm skeleton through hooks.
The template method pattern is one of the most common patterns and is a basic technique for inheritance-based code reuse. The template method pattern requires collaboration between designers who develop abstract classes and concrete subclasses. One designer is responsible for the outline and skeleton of an algorithm, while others are responsible for the logical steps of the algorithm. The primitive methods that represent these specific logical steps are called primitive methods. The method that combines these basic methods is called template method, from which the design pattern gets its name.
For example, we have operations like this: first get some data, then calculate the data, and finally output the data. We have no requirements as to how these operations are implemented (of course, some methods can be implemented in advance), but the sequence logic of these operations is determined and cannot be changed by subclasses:
Abstract template:
// Abstract template
public abstract class AbstractTemplate {
Object data;
// This is the template method
void dealData(a){
getData();
calcData();
printData();
}
// The following are common methods, which may already be implemented or may need to be subclassed
abstract void getData(a);
abstract void calcData(a);
void printData(a){ System.out.println(data); }}Copy the code
Specific template:
// Specify a template
public class Template extends AbstractTemplate {
@Override
void getData(a) {
data = "data";
}
@Override
void calcData(a) { data = (String)data+data; }}Copy the code
Use:
public class TestUse {
public static void main(String args[]){
Template template = newTemplate(); template.dealData(); }}Copy the code
The template method is also relatively simple, but very common, such as the Android Activity lifecycle methods, which are called sequentially, and this design pattern is also used. Similarly, we often use a library of wrapped HTTP requests that we have to write ourselves, but the logic is already specified, as is the template method pattern. In summary, the template method pattern is very widely used.
16. Visitor Pattern
Represent an opration to be performed on the elements of an object structure.Visitor lets you define a new operation without changing the classes of the elements on which it oprates. Represents an operation that operates on elements in an object structure. It can define new operations on individual elements without changing their classes.Copy the code
When to use:
- An object structure, such as a collection, contains many objects, and you want to add some new operations to the objects in the collection.
- The visitor pattern can be used when you need to perform many different and unrelated operations on objects in a collection without wanting to modify the object’s class. The Visitor pattern can centrally define operations on objects in the collection in the Visitor class.
Advantages:
- You can add new operations to an element in a collection without changing the class of that element.
- Some operations of each element in the collection can be centralized into the visitor, which is not only easy to maintain the collection, but also conducive to the reuse of the elements in the collection.
The purpose of the visitor pattern is to encapsulate operations that are imposed on some data structure element. If the operation needs to be modified, the data structure that receives the operation can remain unchanged.
This pattern may be a little bit difficult to understand, so I hope you can read on bit by bit. If you don’t know anything, you can skip it and come back to the example to get a better idea of what’s going on.
Before introducing the visitor pattern, let’s introduce the concept of dispatch.
The Type at which a variable is declared is called the Static Type of the variable, and the Actual Type of the object referenced by the variable is called the Actual Type of the variable. For example:
List<String> list = new ArrayList<String>();
Copy the code
The static type of the list variable is list, and its actual type is ArrayList. The choice of method based on the type of object is Dispatch. There are two types of dispatch: static dispatch and dynamic dispatch.
Static Dispatch occurs at compile time, and Dispatch occurs based on Static type information. Static dispatch is not new to us, and method overloading is static dispatch.
Dynamic Dispatch occurs at run time and dynamically swaps out a method.
Look at an example:
public class Dispatch {
void print(FatherClass c){
System.out.print("Parent");
}
void print(ChildClass c){
System.out.print("Child");
}
public static void main(String args[]){
FatherClass child = new ChildClass();
newDispatch().print(child); child.print(); }}class FatherClass{
void print(a){
System.out.println("Parent"); }}class ChildClass extends FatherClass{
void print(a){
System.out.print("Child"); }}// Output: parent subclass
Copy the code
As you can see, overloads are dispatched based on static types.
Java method rewriting is based on the actual type (dynamic dispatch), the compiler does not know its real type at compile time, but is determined dynamically at runtime.
An object is also known as the receiver of the methods it contains. Dynamic dispatch in Java determines which methods to call based on the actual type of the object.
If can dynamically according to the parameters and the receiver decides to call a method, which is a dynamic multiple dispatch language, if you can according to these two approaches to dynamic decision method calls, dynamic double dispatch, but it has already been said, overloading was conducted according to the static type in Java, so Java can dynamically according to the receiver for method calls, That is, Java is a dynamic single-dispatch language, and if you want to implement double dispatch, you have to do it through design patterns.
OK, getting to the point, the visitor pattern is the pattern that implements double dispatch. In Java, two dispatches are implemented with two method calls.
Since the overload can’t complete dynamic dispatch, we add a Visitor:
public class MultiDispatch {
public static void main(String args[]){
Child child = new Child();
child.print();
child.print(newVistor()); }}class Father{
void print(a){
System.out.println("Parent"); }}class Child extends Father{
void print(a){
System.out.print("Child");
}
void print(Vistor c){
c.print(this); }}class Vistor {
public void print(Child child){ child.print(); }}// Output: subclass subclass
Copy the code
Thus, dynamic double dispatch is done (by calling a method on another object, passing itself in, and calling itself from that method on another class with that parameter passed in). If still not clear, continue to look at the following example). So what does this do? The explanation continues.
For example, we have an app that needs to receive feedback from users, including members and ordinary users. Because there are too many feedbacks, not all feedbacks will be recorded in the effective feedback form. Whether to record valid feedback is usually up to us rather than the users.
In this case, the user is the abstract element:
// Abstract elements
public interface User {
void accept(Visitor visitor);
}
Copy the code
A concrete user is a concrete element, and in this case there are two:
// Common user, specific element
public class UserOrdinary implements User{
String estimation;
public UserOrdinary(String estimation){
this.estimation = estimation;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);The first dispatch is based on the actual type of the receiver when the Accept () method is called, and the second dispatch is through visit.visit (this), passing in the static type, and then calling this in return from the visit() method.
}
String getEstimation(a){
returnestimation; }}Copy the code
//VIP user, specific element
public class UserVIP implements User{
String estimation;
public UserVIP(String estimation){
this.estimation = estimation;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
String getEstimation(a){
returnestimation; }}Copy the code
Access abstractly:
// Abstract the visitor
public interface Visitor {
void visit(UserVIP user);
void visit(UserOrdinary user);
}
Copy the code
For specific visitors, check whether feedback is recorded in effective feedback:
// Specific visitors
public class APPOwner implements Visitor{
@Override
public void visit(UserVIP user) {
String estimation = user.getEstimation();
if(estimation.length()>5)
System.out.println("Record an effective piece of feedback:" + estimation);
}
@Override
public void visit(UserOrdinary user) {
String estimation = user.getEstimation();
if(estimation.length()>10)
System.out.println("Record an effective piece of feedback:"+ estimation); }}Copy the code
Use:
public class TestUse {
public static void main(String args[]){
Visitor appOwner = new APPOwner();
ArrayList<User> users = new ArrayList<User>();
users.add(new UserOrdinary("Short Feedback from Ordinary Users"));
users.add(new UserOrdinary("This is a long feedback from an average user."));
users.add(new UserVIP("Short Feedback from VIP Users"));
users.add(new UserVIP("Long feedback from VIP users."));
Iterator<User> iterator = users.iterator();
while(iterator.hasNext()){ iterator.next().accept(appOwner); }}}Copy the code
Output:
Record a long feedback from a regular user. Record a short feedback from a VIP user. Record a long feedback from a VIP userCopy the code
ArrayList is the structure of objects in the structure. We store elements of different types in the list, and when we visit it, we don’t need a “visitor instanceof SomeClass” statement to do the traversal, because we use the visit() method for specific elements, This is passed to the Visitor, and the Visitor is overloaded with multiple methods, and it’s statically dispatched, and just so the compiler knows the type of this, and its static type is the concrete element itself, and then in the Visitor’s corresponding method, We then call the method in the element with the passed argument (a specific element) to get the data.
In front of the back to double dispatch, as we know, a dynamic multiple dispatch language is very easy to do double dispatch it, can be done by the receiver and the parameters (actual type) to judge the method call, however, Java is a dynamic dispatch, only through the receiver to dynamic allocation, but we also know that now, use the visitor pattern, through two method calls, We still have double dispatch.
17. Adapter Pattern
Translate the interface of a class into another interface that the customer wants. This pattern enables classes that would otherwise not work together due to interface incompatibilities to work together.Copy the code
When to use
- When a program wants to use an existing class, but the interface implemented by that class is different from the interface used by the current program.
advantages
- The target is decoupled from the matched
- Meets the open – close principle
The idea is to retain the services provided by existing classes and modify their interfaces to meet client expectations.
Again, there are object adapters and class adapters, which will be explained in the following code.
For example, you have a player that can only play MP3 music, but now you need it to play FLAC music. We can’t use this player directly, but we can add an adapter to solve this problem:
Original player (adaptor) :
// The adaptor
class Adaptee{
void playMp3(Object src){
System.out.println("Play MP3:"+ src); }}Copy the code
Users like to use features (goals) :
// Target, which is what the user wants to use
interface Target{
void playFlac(Object src);
}
Copy the code
Object adapter:
// Object adapter
public class ObjectAdapter implements Target{
private Adaptee adaptee;
public ObjectAdapter(a){
super(a); adaptee =new Adaptee();
}
@Override
public void playFlac(Object src) {
// SRC may need to be handledadaptee.playMp3(src); }}Copy the code
Class adapter:
// Class adapter
public class ClassAdapter extends Adaptee implements Target {
@Override
public void playFlac(Object src) {
// SRC may need to be handledplayMp3(src); }}Copy the code
Use:
public class TestUse {
public static void main(String args[]){
Adaptee adaptee = new Adaptee();
adaptee.playMp3("mp3");
Target target = new ClassAdapter();
target.playFlac("flac");
target = new ObjectAdapter();
target.playFlac("flac"); }}Copy the code
As you can see, the difference between a class adapter and an object adapter is that a class adapter inherits the adaptor, whereas Java’s is single-inherited, so it is generally better to use an object adapter.
If the number of methods in the target interface is the same as that in the adapter, it is a complete adaptation; if there are more methods in the target interface, it is a residual adaptation; otherwise, it is an incomplete adaptation.
The adapter above is a class, but interfaces can also be used, in which case the implementation is determined by subclasses. An adapter becomes a bidirectional adapter if it implements both the target interface and the interface of the adaptor.
Here’s another example of what might happen in practice:
If we were to extend an old system that uses a Vector as a container and uses Enumeration to iterate through the container, a class that holds a book name. Now, however, our other modules are not communicating clearly that that class uses the LinkedList as an Iterator. In this case, we need an adapter, described in the following code:
Old code for storing books:
// The adaptor
public class Book{
private Vector<String> books;
public Book(a){
books = new Vector<String>();
}
public void add(String book){
books.add(book);
}
public Enumeration<String> getEnum(a){
returnbooks.elements(); }}Copy the code
The client code you write might look something like this
public class TestUse {
public static void main(String args[]){
Book books = new BookAdapter();
books.add("think in java");
books.add("c++ primer");
books.add(Aesop's Fables);
Iterator<String> iterator = books.iterator();
while(iterator.hasNext()){ System.out.println(iterator.next()); }}}Copy the code
To resolve this, we need to add an adapter that converts the behavior of Enumeration to that of Iterator, and then we need to add an adapter for Book (so, the adapter is used twice) :
Iterator adapter (object adapter) :
// The target is Iterator and the target is Enumeration
public class IteratorAdapter implements Iterator<String> {
Enumeration<String> myEnum;
public IteratorAdapter(Enumeration<String> myEnum){
this.myEnum = myEnum;
}
@Override
public boolean hasNext(a) {
return myEnum.hasMoreElements();
}
@Override
public String next(a) {
returnmyEnum.nextElement(); }}Copy the code
Book Adapter (class adapter) :
/ / adapter
public class BookAdapter extends Book implements 可迭代<String>{
@Override
public Iterator<String> iterator(a) {
return newIteratorAdapter(getEnum()); }}Copy the code
Then the client can use it directly, but Book books = new BookAdapter(); BookAdapter books = new BookAdapter(); That is, instead of accessing the object directly, you can access the provided adapter.
18. Composite Pattern
Compose objects into tree structures to represent part-whole hierarchies.Composite lets clients treat individual objects and compositions of objects uniformly. Group objects into a tree structure to represent a partial-whole hierarchy. Composite makes the use of single objects and Composite objects consistent.Copy the code
When to use:
- When you want to represent a partial-whole hierarchy of objects.
- You want users to work with individual objects and composite objects in a consistent way.
Advantages:
- The combination mode contains individual objects and composite objects, and forms a tree structure, so that users can easily deal with individual objects and composite objects.
- Composite objects and individual objects implement the same interface, and users generally do not need to distinguish between individual objects and composite objects.
- When new Composite and Leaf nodes are added, the user’s important code does not need to be changed.
The composite pattern, sometimes called the partial-whole pattern, allows our tree structure problem to blur the concept of simple and complex elements, allowing clients to treat complex elements as if they were simple elements, thus decoupling the internal structure of the client from the complex elements. Composite mode lets you optimize the processing of recursive or hierarchical data structures. There are many examples of hierarchical data structures that make composite patterns very useful. A common example of hierarchical data structures is the file system of a computer. Let’s use this example to illustrate the composite pattern (although we can describe it directly using a data structure like Tree).
In a file system, directories contain files and directories
Abstract interfaces for directories and files (abstract components) :
// Abstract the component
public interface Component {
void addFile(Component file);
Component addFolder(Component folder);
void removeFile(Component file);
void removeFolder(Component folder);
List<Component> getFiles(a);
List<Component> getFolders(a);
List<Component> getAll(a);
Iterator<Component> iterator(a);
void display(a);
}
Copy the code
Directory (Composite node) :
public class Folder implements Component {
private String name;
private List<Component> files;
private List<Component> folders;
public Folder(String name){
this.name = name;
files = new ArrayList<Component>();
folders = new ArrayList<Component>();
}
@Override
public void addFile(Component file) {
files.add(file);
}
@Override
public Component addFolder(Component folder) {
folders.add(folder);
return this;
}
@Override
public void removeFile(Component file) {
files.remove(file);
}
@Override
public void removeFolder(Component folder) {
folders.remove(folder);
}
@Override
public List<Component> getFiles(a) {
return files;
}
@Override
public List<Component> getFolders(a) {
return folders;
}
@Override
public List<Component> getAll(a) {
List<Component> all = new ArrayList<Component>(folders);
all.addAll(files);
return all;
}
@Override
public Iterator<Component> iterator(a) {
List<Component> all = new ArrayList<Component>();
add(all,this);
return all.iterator();
}
private void add(List<Component> all,Component component){
if(component==null) return;
all.add(component);
Iterator<Component> iterator = component.getFolders().iterator();
while(iterator.hasNext()){
add(all,iterator.next());
}
all.addAll(component.getFiles());
}
@Override
public void display(a) { System.out.println(name); }}Copy the code
File (Leaf node) :
/ / the Leaf nodes
public class File implements Component{
private String name;
public File(String name){
this.name = name;
}
@Override
public void addFile(Component file) {}
@Override
public Component addFolder(Component folder) { return null; }
@Override
public void removeFile(Component file) {}
@Override
public void removeFolder(Component folder) {}
@Override
public List<Component> getFiles(a) { return null; }
@Override
public List<Component> getFolders(a) { return null; }
@Override
public List<Component> getAll(a) { return null; }
@Override
public Iterator<Component> iterator(a) { return null; }
@Override
public void display(a) { System.out.println(name); }}Copy the code
Use:
public class TestUse {
public static void main(String args[]){
Component root = new Folder("root");/ / root directory
Component folder1 = new Folder("java");
Component folder2 = new Folder("c++");
Component folder3 = new Folder("c#");
Component file1 = new File("info.txt");
root.addFolder(folder1).addFolder(folder2).addFolder(folder3).addFile(file1);// Add a level 1 directory
folder1.addFile(new File("info.java"));
Iterator<Component> iterator = root.iterator();
while(iterator.hasNext()){
Component component = iterator.next();
if(component instanceof Folder)
System.out.print("folder:");
else
System.out.print("file:"); component.display(); }}}Copy the code
Console output:
Folder: root Folder: Java file: info. Java Folder: C ++ Folder: C#File: info. TXTCopy the code
The output is the same as the iterator we would expect (starting with a directory, first printing the directory name, then recursively moving to the next directory if there is one, and then printing a list of files if there is none).
Files and directories (Composite and Leaf) implement the same interface, so it’s easy to operate, including iterating.
19. Proxy Pattern
Provide a surrogate or placeholder for another object to control access to it. Provide a proxy for other objects to control access to this object.Copy the code
When to use:
- The program may not want the user to access the object directly, but rather provide a special object to control access to the current object.
- If an object (such as a large image) takes a long time to load.
- If the object is on a remote host, you need to provide users with the ability to access the remote object.
Advantages:
- The proxy mode can mask the object that the user is actually requesting and decouple the user program from the object in question.
- Use proxies to act as proxies for objects that take time to create.
A user does not want or can’t directly reference an object (or the designer does not want the user to directly access the object), whereas a proxy object can act as an intermediary between the client and the target object. And in this proxy object, we can do a lot more.
The proxy pattern implementation is actually quite simple. The implementation is shown directly in code below:
Abstract objects:
// Abstract objects
public interface AbstractObject {
void method1(a);
int method2(a);
void method3(a);
}
Copy the code
Specific Objects:
// Specific object
public class TargetObject implements AbstractObject {
@Override
public void method1(a) {
System.out.println("Object specific method 1");
}
@Override
public int method2(a) {
System.out.println("Object specific method 2");
return 0;
}
@Override
public void method3(a) {
System.out.println("Object specific method 3"); }}Copy the code
Proxy object:
// Proxy object
public class ProxyObject implements AbstractObject {
AbstractObject object = new TargetObject();
@Override
public void method1(a) {
object.method1();
}
@Override
public int method2(a) {
return object.method2();
}
@Override
public void method3(a) {
System.out.println("Action before calling target object");
object.method3();
System.out.println("Action after calling target object"); }}Copy the code
Client (user) use:
public class TestUse {
public static void main(String args[]){
AbstractObject obj = newProxyObject(); obj.method1(); obj.method2(); obj.method3(); }}Copy the code
Leaving the console output aside, you can see that the proxy mode implementation is indeed simpler and has obvious benefits, isolating the target object from the user it is using, and performing additional operations (somewhat aop in mind) before and after calling the target object’s methods.
LinkedList can implement stacks, queues, and other data structures, but we still want a specific queue or data structure. Here’s how to implement stacks and queues as proxies (partial proxies) :
/ / use LinkedList
public class TestUse {
public static void main(String[] args) {
/ / stack
Stack<Integer> stack = new Stack<Integer>();
for(int i=0; i<10; i++) stack.push(i); System.out.println(stack.peek()); System.out.println(stack.peek()); System.out.println(stack.pop()); System.out.println(stack.pop()); System.out.println(stack.pop()); Iterator<Integer> iterator = stack.iterator();while(iterator.hasNext())
System.out.print(iterator.next());
System.out.println();
/ / the queue
Queue<Integer> queue = new Queue<Integer>();
for(int i=0; i<10; i++) queue.enqueue(i); System.out.println(queue.peek()); System.out.println(queue.peek()); System.out.println(queue.dequeue()); System.out.println(queue.dequeue()); System.out.println(queue.dequeue()); iterator = queue.iterator();while(iterator.hasNext()) System.out.print(iterator.next()); }}// Use the proxy pattern to implement a stack using LinkedList
class Stack<T> implements 可迭代<T>{
private LinkedList<T> stack = new LinkedList<T>();
public T pop(a){// Remove the top element of the stack
return stack.poll();
}
public T peek(a){// Remove the top element from the stack
return stack.peek();
}
public void push(T t){/ / into the stack
stack.push(t);
}
@Override
public Iterator<T> iterator(a) {
returnstack.iterator(); }}// Use the proxy pattern to implement a queue using LinkedList
class Queue<T> implements 可迭代<T>{
private LinkedList<T> queue = new LinkedList<T>();
public void enqueue(T t){
queue.offer(t);
}
public T dequeue(a){
return queue.poll();
}
public T peek(a){
return queue.peek();
}
@Override
public Iterator<T> iterator(a) {
returnqueue.iterator(); }}Copy the code
This makes the use of queues and stacks more explicit.
There are also agents called remote agents that use RMI and can then call object methods on another JVM on the network from within the program. There is not much description here, but you can search RMI materials for yourself.
20. Plyweight Pattern
Use sharing to support large numbers of fine-grained objects efficiently. Use sharing techniques to effectively support a large number of fine-grained objects.Copy the code
When to use:
- When an application uses a large number of objects, some of which have essentially the same properties among them, share elements should be used to encapsulate the same parts.
- Most of the state of an object can be changed to an external state, and you can consider using such an object as a shared element in the system.
Advantages:
- Using primitives saves memory and is particularly good for dealing with large numbers of fine-grained objects where many of the property values are the same and cannot be changed once created.
- A share in a share pattern can receive data in the external state using the method’s parameters, but the external state data does not interfere with the internal data in the share, which allows the share to be shared in different environments.
In the JAVA language, the String type uses the meta-schema. String objects are final and cannot be changed once they are created. In JAVA, string constants are stored in a constant pool. JAVA ensures that there is only one copy of a string constant in the constant pool. String STR =” String “, where “STR” is a String constant.
The Enjoy mode includes three roles:
- Share element interface (Plyweight) : defines public methods to get its internal data and receive external data.
- Concrete Plyweight: Implementation of the share element interface.
- A Plyweight Factory: An instance of this class is responsible for creating and managing share objects, which users or other objects must request to obtain a share object.
Let’s look at a simple implementation of the share pattern:
// Simple share mode
public class SimpleFlyweight {
public static void main(String args[]){
FlyweightFactory factory = new FlyweightFactory();
IFlyweight flyweight1,flyweight2,flyweight3,flyweight4;
flyweight1 = factory.getFlyweight("value1");
flyweight2 = factory.getFlyweight("value1");
flyweight3 = factory.getFlyweight("value1");
flyweight4 = factory.getFlyweight("value2"); flyweight1.doSomething(); flyweight2.doSomething(); flyweight3.doSomething(); flyweight4.doSomething(); System.out.println(factory.size()); }}// Share the element interface
interface IFlyweight{
void doSomething(a);
}
// Specify the value
class Flyweight implements IFlyweight{
private String value;
public Flyweight(String value){
this.value = value;
}
@Override
public void doSomething(a) { System.out.println(value); }}// Xiangyuan factory
class FlyweightFactory{
HashMap<String, IFlyweight> flyweights = new HashMap<String, IFlyweight>();
IFlyweight getFlyweight(String value){
IFlyweight flyweight = flyweights.get(value);
if(flyweight == null){
flyweight = new Flyweight(value);
flyweights.put(value, flyweight);
}
return flyweight;
}
public int size(a){
returnflyweights.size(); }}Copy the code
In the example above, we created a total of 4 specific privileges, and we output the number of objects created is 2, this is the privilege mode. You can reduce the number of objects created.
The example is easy to implement because the concrete share object only takes a String as a member. We need to check a lot of weather data, mainly weather and temperature, but the combination of weather and temperature can easily be the same. To reduce the number of objects created, we use the share mode:
The first is the abstract weather interface:
// Share the element interface
public interface IWeather {
void printWeather(a);
}
Copy the code
Then there is the realization of the weather. Attention! Here we use a HashMap to hold a reference to the element. The weather is determined by weather conditions and temperature. We need two values for the key, but the key can only be one object, so we finally choose this object as the key. The HashMap Key is limited and must properly provide the hashCode() method (on which the HashMap accesses data) and equals() method (which the HashMap calls to determine whether the Key is equal when it passes a Key value). Both methods are implemented in the following implementation:
// Specify the value
public class Weather implements IWeather{
private String weather;
private Integer temperature;
public Weather(String weather,int temperature){
this.weather = weather;
this.temperature = temperature;
}
@Override
public void printWeather(a) {
System.out.print("Weather." + weather);
System.out.println("Temperature." + temperature);
}
@Override
public boolean equals(Object obj) {// The object is the same only if both values are equal
Weather weather = (Weather)obj;
return weather.weather.equals(this.weather)&&weather.temperature==temperature;
}
@Override
public int hashCode(a) {// Both the Integer and String hashCode() methods are reasonable enough to take an average
return (weather.hashCode()+temperature.hashCode())/2; }}Copy the code
Next is The Yuan Factory, which produces specific weather for us:
// Xiangyuan factory
public class WeatherFactory {
private HashMap<IWeather, IWeather> weathers;
public WeatherFactory(a){
weathers = new HashMap<IWeather, IWeather>();
}
public IWeather getFlyWeight(String weather,int temperature){
Weather objectWeather = new Weather(weather, temperature);
IWeather flyweight = weathers.get(objectWeather);
if(flyweight == null){
flyweight = objectWeather;
weathers.put(objectWeather, flyweight);
}
else objectWeather = null;// Facilitate gc collection
return flyweight;
}
public int getFlyweightSize(a){
returnweathers.size(); }}Copy the code
Finally, use:
public class TestUse {
public static void main(String args[]){
WeatherFactory factory = new WeatherFactory();
IWeather weather1,weather2,weather3,weather4,weather5,weather6,weather7,weather8;
weather1 = factory.getFlyWeight("Cloudy".15);
weather2 = factory.getFlyWeight("Fine".23);
weather3 = factory.getFlyWeight("Cloudy".16);
weather4 = factory.getFlyWeight("Yin".10);
weather5 = factory.getFlyWeight("Cloudy".15);
weather6 = factory.getFlyWeight("Cloudy".15);
weather7 = factory.getFlyWeight("Cloudy".15);
weather8 = factory.getFlyWeight("Cloudy".15);
weather1.printWeather();
weather2.printWeather();
weather3.printWeather();
weather4.printWeather();
weather5.printWeather();
weather6.printWeather();
weather7.printWeather();
weather8.printWeather();
System.out.println("Actual number of objects"+ factory.getFlyweightSize()); }}Copy the code
Output:
Weather: cloudy Temp :15 Weather: sunny Temp :23 Weather: cloudy Temp :16 Weather: Cloudy Temp :10 Weather: cloudy Temp :15 Weather: cloudy Temp :15 Actual number of objects 4Copy the code
As you can see, exactly as expected, the same object is not created twice.
The advantage of the metadata pattern is that it drastically reduces the number of objects in memory. However, it does so at a high cost: the share pattern makes the system more complex. In order for objects to be shareable, some state needs to be externalized, which complicates the program logic. The share mode externalizes the state of the share object, and reading the external state makes the run time longer.
21. Appearance mode
Providing a consistent interface for a set of interfaces in the system, the Facade pattern defines a high-level interface that makes the subsystem easier to use.Copy the code
When to use
- When needed. Too many times
advantages
- From your presentation below, you can see that appearance mode can make a system easier to use
The appearance pattern is a very simple pattern that we use consciously or unconsciously.
A feature we provide to the user may contain many steps, but we can encapsulate these steps into a unified interface, so that the user feels like a single operation, so that it is easier to use.
For example, when we went back a few years, not everyone could use a camera, and you had to focus before you pressed the shutter. Now, you just press the shutter, and everything in between is done automatically. The facade pattern provides just such a mechanism.
We are using an example with code: when a user wants to purchase an item, the system will first judge the inventory, then calculate the cost (item price, postage, discounts, etc.), and finally generate an order. This consists of several subsystems, the inventory management subsystem and the billing subsystem, which in turn is divided into smaller subsystems (both of which use appearance mode).
Subsystem to get the original price of an item:
// Get the price of the item
public class ProductPrice {
int getPrice(String product){
return Math.abs(product.hashCode());// Get commodity prices}}Copy the code
Subsystem to get postage:
// Calculate the postage
public class Postage {
int getPostage(String addr){
return Math.abs(addr.hashCode())%20+6;// Simulate postage calculation}}Copy the code
Subsystem for calculating preferences:
// Calculate discount
public class Discount {
int getDiscount(String discountCode){
return Math.abs(discountCode.hashCode())%3; }}Copy the code
The subsystem that calculates the final price is also a facade because it contains references to the above three subsystems:
// The accounting subsystem
public class FinalPrice {
ProductPrice productPrice;
Postage postage;
Discount discount;
public FinalPrice(a){
productPrice = new ProductPrice();
postage = new Postage();
discount = new Discount();
}
int getFinalPrice(String product,String addr,String discountCode){
returnproductPrice.getPrice(product)+postage.getPostage(addr)-discount.getDiscount(discountCode); }}Copy the code
Inventory subsystem:
// Inventory subsystem
public class Stock {
boolean hasStock(String product){
return new Random().nextInt(Math.abs(product.hashCode()))>0;// Simulate whether there is still inventory}}Copy the code
Finally, there is the user-facing purchase interface, which is also a facade here, and uses the singleton pattern implemented by enumeration:
/ / appearance
public enum ProductSalesman {
instance;
Stock stock = new Stock();
FinalPrice finalPrice = new FinalPrice();
Object buySomething(String product,String addr,String discountCode){
if(! stock.hasStock(product))return "Out of stock";
int price = finalPrice.getFinalPrice(product, addr, discountCode);
return "Order Information :" + product + "-" + addr + "-" + discountCode + "-"+ price; }}Copy the code
It’s easy to use, the user doesn’t need to know how to operate internally, just use the purchase interface:
public class TestUse {
public static void main(String args[]){
Object info = ProductSalesman.instance.buySomething(Galactica."The earth"."K1234523"); System.out.println(info); }}Copy the code
The appearance pattern is used in two of the examples above, which shows how common it is.
Of course, this would be more cumbersome if we wanted to add subsystems, but it would be much easier to use if we abstracting an interface for the subsystem and then incorporating a variation of the chain of responsibility pattern that terminates the delivery of the request on failure.
22. Bridge Pattern
Aliases: handle mode (Another Name: (49) Goldman an abstraction from its implementation so that the two can vary independently. Separate the abstract part from its implementation part so that they can be changed independently.Copy the code
When to use:
- You don’t want abstraction to be bound to some important implementation code; this part of the implementation can be dynamically determined at runtime.
- Both the abstract and implementer can inherit. When methods are extended independently of each other, a program may need to dynamically combine an instance of an abstract subclass with an instance of an implementer’s subclass at runtime.
- Changes to implementor-level code are expected to have no impact on the abstraction layer, meaning that the abstraction layer code does not need to be recompiled, and vice versa.
Advantages:
- The bridge pattern separates implementation and abstraction so that abstraction can be implemented and extended independently. When you modify the implemented code, you don’t affect the abstract code, and vice versa.
- To meet the open-closed principle, the abstraction and implementer are at the same level, so that the system can independently expand the two levels. Adding a new concrete implementer does not require modification of the refinement abstraction, and adding a new refinement abstraction does not require modification of the concrete implementation.
The bridge pattern is a structural pattern that deals with the need for a class to have two or more dimensional changes that inheritance alone would not be able to fulfill or would make the design rather bloated.
The approach of the bridge pattern is to abstract out the changing part and separate the changing part from the main class, thus completely separating the change from the multiple dimensions. Finally, provide a management class that combines changes on different dimensions to meet the needs of the business.
There are four roles in bridge mode:
- abstract
- Refine the abstract
- The implementer
- Implementor
Here’s an example (not lenovo or AMD, just an example…) :
// Bridge mode
public class SimpleBridge {
public static void main(String args[]){
new LenevoComputer(new Amd()).discribe();
new HaseeComputer(newIntel()).discribe(); }}/ / the implementer
interface Cpu{
String discribe(a);
}
// Implementer *2
class Amd implements Cpu{
public String discribe(a) {
return "just so so..."; }}class Intel implements Cpu{
public String discribe(a) {
return "great !"; }}/ / abstract
abstract class AbstractComputer{
Cpu cpu;
public AbstractComputer(Cpu cpu){
this.cpu=cpu;
}
public abstract void discribe(a);
}
// Refine the abstraction *2
class LenevoComputer extends AbstractComputer{
public LenevoComputer(Cpu cpu) {
super(cpu);
}
@Override
public void discribe(a) {
System.out.println("Lenovo Notebook CPU :"+super.cpu.discribe()); }}class HaseeComputer extends AbstractComputer{
public HaseeComputer(Cpu cpu) {
super(cpu);
}
@Override
public void discribe(a) {
System.out.println("Shenzhou notebook CPU :"+super.cpu.discribe()); }}// Output: Lenovo notebook CPU :just so so... Shenzhou notebook CPU: Great!
Copy the code
The above abstraction is variable, and the implementation is variable, so you can apply the bridge pattern.
Let’s take a real example of data storage again. Data storage can be implemented as a file or database, and we can choose to store it locally or on the network, as follows:
The first is to define the implementation of the data store:
/ / implementation
public interface ISaveData {
void save(Object data);
}
Copy the code
Then there is the concrete implementation:
// Implement it
public class SaveToFile implements ISaveData{
@Override
public void save(Object data) {
System.out.println(data + "Save to file"); }}Copy the code
// Implement it
public class SaveToDB implements ISaveData{
@Override
public void save(Object data) {
System.out.println(data + "Store to database"); }}Copy the code
Then there is abstraction, how to store:
/ / abstract
public abstract class AbstractSave {
ISaveData saveData;
public AbstractSave(ISaveData saveData){
this.saveData = saveData;
}
public abstract void save(a);
}
Copy the code
Next is the refinement abstraction:
// Refine the abstraction
public class LocalSave extends AbstractSave{
public LocalSave(ISaveData saveData) {
super(saveData);
}
@Override
public void save(Object data) {
System.out.print("Local storage:"); saveData.save(data); }}Copy the code
// Refine the abstraction
public class NetSave extends AbstractSave{
public NetSave(ISaveData saveData) {
super(saveData);
}
@Override
public void save(Object data) {
System.out.print("Network storage:"); saveData.save(data); }}Copy the code
Use:
public class TestUse {
public static void main(String args[]){
Object data = "Data";
ISaveData saveDataDb = new SaveToDB();
ISaveData saveDataFile = new SaveToFile();
AbstractSave save;
save = new NetSave(saveDataDb);
save.save(data);
save = new NetSave(saveDataFile);
save.save(data);
save = new LocalSave(saveDataDb);
save.save(data);
save = newLocalSave(saveDataFile); save.save(data); }}Copy the code
Output:
Network storage: Data is stored to the database network storage: data is stored to files Local storage: Data is stored to the database Local storage: data is stored to filesCopy the code
And the program is easy to extend by adding refinement abstractions and concrete implementations.
23, Decorator Pattern
Aliases: Wrappers dynamically add additional responsibilities to objects. In terms of functionality, decoration mode is more flexible than the production subclass.Copy the code
When to use
- When a program wants to dynamically enhance the functionality of one pair of objects of a class without affecting other objects
- Using inheritance to enhance object functionality is not conducive to system expansion and maintenance
advantages
- The decorator and the decorator are loosely coupled.
- Meets the open – close principle
- Multiple concrete decorators can be used to decorate instances of specific components
The decorator pattern uses an instance of a subclass of the decorator class to delegate client calls to the decorator class. The key to the decorator pattern is that this extension is completely transparent. Decorator and decorator share a common superclass, and the purpose of inheritance is to inherit types, not behaviors.
For example, we have a tool for persistent data, the most began to design the function is the persistence of data to a local file, but now some data needs to be persisted to the database at the same time, later, and data needs to be persisted to the location on the network at the same time, and we can’t change before the implementation of, because behind these requirements, just for some data, So we can use decorator mode naturally.
The data persistence interface (abstract component) is as follows:
// Decorator interface
public interface IPersistentUtil {
void persistentMsg(String msg);
}
Copy the code
The initial implementation (specific components) is as follows:
// Concrete to be decorated
public class PersistentUtil implements IPersistentUtil{
@Override
public void persistentMsg(String msg) {
System.out.println(msg + "File in"); }}Copy the code
First, we need to abstract a decorator, which we can’t use directly (even if we use it with an anonymous inner class, it makes no sense as originally implemented) because it is an abstract class (decorator) :
/ / decoration
public abstract class PersistentDecorator implements IPersistentUtil {
IPersistentUtil iPersistentUtil;
public PersistentDecorator(IPersistentUtil iPersistentUtil){
this.iPersistentUtil = iPersistentUtil;
}
@Override
public void persistentMsg(String msg) { iPersistentUtil.persistentMsg(msg); }}Copy the code
Then, we add a decorator that can also persist data to the database (specific decorator) :
// Decorate -- store database
public class PersistentDbDecorator extends PersistentDecorator {
public PersistentDbDecorator(IPersistentUtil iPersistentUtil){
super(iPersistentUtil);
}
@Override
public void persistentMsg(String msg) {
iPersistentUtil.persistentMsg(msg);
persistentToDb(msg);
}
private void persistentToDb(String msg){
System.out.println(msg + "Put it in the database"); }}Copy the code
Later, we added a decorator (concrete decorator) that can persist data simultaneously on the network store:
// Decorate -- save elsewhere in the network
public class PersistentNetDecorator extends PersistentDecorator {
public PersistentNetDecorator(IPersistentUtil iPersistentUtil){
super(iPersistentUtil);
}
@Override
public void persistentMsg(String msg) {
iPersistentUtil.persistentMsg(msg);
persistentToNet(msg);
}
private void persistentToNet(String msg){
System.out.println(msg + "Stored elsewhere in the network."); }}Copy the code
The next step is to use and verify:
public class TestUse {
public static void main(String args[]){
// The decorator
final String data = "Data";
IPersistentUtil iPersistentUtil = new PersistentUtil();
iPersistentUtil.persistentMsg(data);
System.out.println("Now decorate database persistence:");
iPersistentUtil = new PersistentDbDecorator(iPersistentUtil);
iPersistentUtil.persistentMsg(data);
System.out.println("Next, decorate network storage persistence:");
iPersistentUtil = newPersistentNetDecorator(iPersistentUtil); iPersistentUtil.persistentMsg(data); }}Copy the code
Console output:
The data is stored in a file and the database is decorated for persistence: the data is stored in a file and the data is stored in a database and the data is stored elsewhere on the networkCopy the code
As you can see, objects that use decorators have been enhanced and can use multiple decorators.
In the java.io package, many classes are designed with decorator mode (for example, Reader is the abstract decorator, FileReader is the concrete decorator, BufferedReader is the decorator). If you are interested, you can look at the source code.
conclusion
Well, that concludes the application of the 23 design patterns in Java.
Writing code should not be too rigid (it should be), and just like the design patterns above, we should not follow them exactly, and often need to be flexible. For example, in the singleton pattern, we can use a similar principle. Instead of letting objects be created uncontrollably, we can create as many instances as we want, which is controllable. This is obviously not the singleton pattern description, but it can sometimes be beneficial.
A lot of patterns have a lot in common with each other, and with a little implementation modification, it becomes another pattern. Patterns should not be abused, but when we design, it may be simpler, more convenient, and easier to maintain by giving a guide to implement requirements in a certain way.
Source code all on Github: click to get github source code
This article from: CSDN:http://blog.csdn.net/anxpp/article/details/51224293