Design Patterns
- The basics of reusable object-oriented software
Design pattern is a set of repeated use, known to most people, classified cataloging, and code design experience. The use of design patterns is to reuse the code, make the code easier to understand by others, and ensure the reliability of the code. There is no doubt that design patterns are win-win for themselves, others and systems. Design patterns make code compilation truly engineered. Design patterns are the cornerstone of software engineering, just like bricks and stones in a building. The rational use of design patterns in the project can perfectly solve many problems. Each pattern now has corresponding principles to correspond to it. Each pattern describes a problem that is constantly repeated around us and the core solution of the problem, which is also the reason why it can be widely used. This chapter is the design model of the Beauty of Java [Evolution from Rookie to Expert] series. We will study this chapter in a combination of theory and practice. I hope that program enthusiasts will learn the design model well and be an excellent software engineer!
If you have any questions during the reading process, please contact: egg in time.
Email: [email protected] Weibo: http://weibo.com/xtfggef
If reprinted, please indicate the source: http://blog.csdn.net/zhangerqing
Practical combat of enterprise-level projects (with source code) address: http://zz563143188.iteye.com/blog/1825168
Operation and maintenance knowledge sorting http://zz563143188.iteye.com/blog/2094335
23 modes of Java to implement source code and collect development data for five years download address: http://pan.baidu.com/share/home?uk=4076915866&view=share
1. Classification of design patterns
Overall, design patterns are divided into three categories:
There are five types of creation modes: factory method mode, abstract factory mode, singleton mode, builder mode, and prototype mode.
There are seven structural modes: adapter mode, decorator mode, proxy mode, appearance mode, bridge mode, combination mode, and enjoyment mode.
Behavioral modes, a total of eleven: policy mode, template method mode, observer mode, iterative sub-mode, responsibility chain mode, command mode, memo mode, status mode, visitor mode, mediator mode, and interpreter mode.
In fact, there are two other categories: concurrent mode and thread pool mode. Let's use a picture to describe it as a whole:
2. Six principles of design model
1. Open Close Principle
The principle of opening and closing is to open to extensions and close to modifications. When the program needs to be expanded, you cannot modify the original code to achieve a hot plug effect. So in a word, it is: in order to make the program more extensible and easy to maintain and upgrade. To achieve such an effect, we need to use interfaces and abstract classes, which we will mention in the specific design later.
2. Liskov Substitution Principle
Liskov Substitution Principle LSP is one of the basic principles of object-oriented design. The Rich substitution principle says that any base class can appear, subclasses can definitely appear. LSP is the cornerstone of inheritance and reuse. Only when the derivative class can replace the base class and the functions of the software unit are not affected can the base class be truly reused, and the derivative class can also add new behaviors based on the base class. The Richter substitution principle is a supplement to the "open-closing" principle. The key step to implementing the "open-close" principle is abstraction. The inheritance relationship between base class and subclass is the concrete implementation of abstraction, so the Rich substitution principle is a standardization of the specific steps to implement abstraction. ― From Baidu Encyclopedia
3. Dependence Inversion Principle
This is the basis of the principle of opening and closing. The specific content: true programming of interfaces depends on abstraction rather than concrete.
4. Interface Segregation Principle
This principle means: using multiple isolated interfaces is better than using a single interface. It also means reducing the coupling degree between classes. From here we can see that the design pattern is actually the design idea of a software, starting from a large software architecture, for the convenience of upgrading and maintenance. Therefore, the above article has appeared many times: reduce dependence and reduce coupling.
5. Demeter Principle
Why is the principle of least knowledge? That is, one entity should interact with other entities as little as possible, so that the system functional modules are relatively independent.
6. Composite Reuse Principle
The principle is to try to use synthesis/aggregation methods rather than inheritance.
3. Java's 23 design patterns
Starting from this section, we introduce in detail the concepts, application scenarios, etc. of 23 design patterns in Java, and analyze them in combination with their characteristics and the principles of design patterns.
1. Factory Method
There are three types of factory method modes:
11. The ordinary factory model is to establish a factory class and create instances of some classes that implement the same interface. First look at the relationship diagram:
For example: (Let’s give an example of sending emails and text messages)
First, create a common interface between the two:
public interface Sender { public void Send(); } Secondly, create an implementation class:
public class MailSender implements Sender { @Override public void Send() { System.out.println("this is mailsender!"); } } public class SmsSender implements Sender { @Override public void Send() { System.out.println("this is sms sender!"); } } Finally, factory construction:
public class SendFactory { public Sender produce(String type) { if ("mail".equals(type)) { return new MailSender(); } else if ("sms".equals(type)) { return new SmsSender(); } else { System.out.println("Please enter the correct type!"); return null; } } } Let's test it:
public class FactoryTest { public static void main(String[] args) { SendFactory factory = new SendFactory(); Sender sender = factory.produce("sms"); sender.Send(); } } Output: this is sms sender!
22. Multiple factory method modes are an improvement to the ordinary factory method mode. In the ordinary factory method mode, if the passed string is wrong, the object cannot be created correctly. The multiple factory method modes provide multiple factory methods to create objects separately. Relationship diagram:
Just modify the above code and change the SendFactory class, as follows:
view plaincopypublic class SendFactory { public Sender produceMail(){ return new MailSender(); } public Sender produceSms(){ return new SmsSender(); } } The test class is as follows:
public class FactoryTest { public static void main(String[] args) { SendFactory factory = new SendFactory(); Sender sender = factory.produceMail(); sender.Send(); } } Output: this is mailsender!
33. Static factory method mode. Set the methods in the above multiple factory method modes to static. There is no need to create an instance, just call it directly.
public class SendFactory { public static Sender produceMail(){ return new MailSender(); } public static Sender produceSms(){ return new SmsSender(); } }
public class FactoryTest { public static void main(String[] args) { Sender sender = SendFactory.produceMail(); sender.Send(); } } Output: this is mailsender!
Overall, the factory model is suitable: when a large number of products need to be created and have a common interface, it can be created through the factory method model. Among the above three modes, the first one cannot create the object correctly if the string passed in is incorrect, and the third one does not need to instantiate the factory class compared to the second one. Therefore, in most cases, we will choose the third one - the static factory method mode.
2. Abstract Factory pattern
There is a problem with the factory method model, which is that the creation of a class depends on the factory class. That is to say, if you want to expand the program, you must modify the factory class, which violates the closure principle. Therefore, from a design perspective, there are certain problems. How to solve it? This uses the abstract factory pattern to create multiple factory classes. In this way, once new functions are needed, you can add new factory classes directly, without modifying the previous code. Because abstract factories are not easy to understand, we first look at the diagram and then follow the code, which is easier to understand.
Please see the example:
public interface Sender { public void Send(); } Two implementation classes:
public class MailSender implements Sender { @Override public void Send() { System.out.println("this is mailsender!"); } } public class SmsSender implements Sender { @Override public void Send() { System.out.println("this is sms sender!"); } } Two factory categories:
public class SendMailFactory implements Provider { @Override public Sender produce(){ return new MailSender(); } }
public class SendSmsFactory implements Provider{ @Override public Sender produce() { return new SmsSender(); } } Provide an interface:
public interface Provider { public Sender produce(); } Test class:
public class Test { public static void main(String[] args) { Provider provider = new SendMailFactory(); Sender sender = provider.produce(); sender.Send(); } } In fact, the advantage of this model is that if you want to add a function now: send timely information, you only need to make an implementation class, implement the Sender interface, and at the same time make a factory class, implement the Provider interface, which is OK, and there is no need to change the ready-made code. Doing this will make it more scalable!
3. Singleton mode
Singleton is a commonly used design pattern. In Java applications, a singleton object can ensure that in a JVM, there is only one instance of the object. This model has several benefits:
1. Some classes are created more frequently, and for some large objects, this is a huge system overhead.
2. The new operator is eliminated, the frequency of system memory is reduced, and the pressure of GC is reduced.
3. Some categories, such as the core trading engine of the exchange, control the trading process. If multiple categories can be created, the system will be completely messed up. (For example, if multiple commanders appear in an army command at the same time, it will definitely be in chaos), so only by using the singleton model can we ensure that the core transaction server independently controls the entire process.
First, let's write a simple singleton class:
public class Singleton { /* Hold a private static instance to prevent reference. The value here is null, with the purpose of achieving lazy loading*/ private static Singleton instance = null; /* Private constructor to prevent instantiation*/ private Singleton() { } /* Static engineering method to create an instance*/ public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } /* If the object is used for serialization, it can be ensured that the object remains consistent before and after serialization*/ public Object readResolve() { return instance; } } This class can meet the basic requirements, but if we put this class with a wireless wireless security protection in a multi-threaded environment, there will definitely be problems. How to solve it? First we will think of adding synchronized keyword to getInstance method, as follows:
public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } However, the synchronized keyword locks this object. This usage will decrease in performance, because every time you call getInstance(), the object must be locked. In fact, only when the object is created for the first time, it is not necessary to be locked, so this place needs to be improved. Let's change it to the following:
public static Singleton getInstance() { if (instance == null) { synchronized (instance) { if (instance == null) { instance = new Singleton(); } } } return instance; } It seems to solve the problem mentioned earlier, adding the synchronized keyword inside, that is, there is no need to lock when calling, only locking is required when instance is null and objects are created, which has a certain improvement in performance. However, in this case, there may be problems. Look at the following situation: creating objects and assignment operations in Java instructions are performed separately, that is, the instance = new Singleton(); statement is executed in two steps. However, the JVM does not guarantee the order of these two operations, which means that it is possible that the JVM will allocate space for the new Singleton instance, then directly assign it to the instance member, and then initialize the Singleton instance. This may make an error. Let’s take A and B threads as examples:
A>A and B threads enter the first if judgment at the same time
b>A first enters the synchronized block, since instance is null, it executes instance = new Singleton();
c> Due to the optimization mechanism inside the JVM, the JVM first draws some blank memory allocated to the Singleton instance and assigns it to the instance member (note that the JVM does not start initializing this instance at this time), and then A leaves the synchronized block.
d>B enters the synchronized block. Since instance is not null at this time, it immediately leaves the synchronized block and returns the result to the program that called the method.
e>At this time, thread B intends to use the Singleton instance, but finds that it has not been initialized, so an error occurs.
Therefore, there are still possible errors in the program. In fact, the running process of the program is very complicated. From this point, we can see that especially in writing programs in a multi-threaded environment is more difficult and challenging. We further optimized the program:
private static class SingletonFactory{ private static Singleton instance = new Singleton(); } public static Singleton getInstance(){ return SingletonFactory.instance; } The actual situation is that the singleton pattern uses internal classes to maintain the implementation of singletons. The internal mechanism of the JVM can ensure that when a class is loaded, the loading process of this class is mutually exclusive to threads. In this way, when we call getInstance for the first time, the JVM can help us ensure that the instance is created only once and will ensure that the memory assigned to the instance is initialized, so we don't have to worry about the above problems. At the same time, this method will only use the mutual exclusion mechanism when it is called for the first time, which solves the problem of low performance. In this way, we temporarily summarize a perfect singleton pattern:
public class Singleton { /* Private constructor method to prevent instantiation*/ private Singleton() { } /* Use an internal class here to maintain a singleton*/ private static class SingletonFactory { private static Singleton instance = new Singleton(); } /* Get instance*/ public static Singleton getInstance() { return SingletonFactory.instance; } /* If the object is used for serialization, it can be ensured that the object remains consistent before and after serialization*/ public Object readResolve() { return getInstance(); } } In fact, it is not necessarily true that it is perfect. If an exception is thrown in the constructor, the instance will never be created and there will be an error. Therefore, there is nothing perfect, we can only choose the implementation method that is most suitable for our application scenario based on the actual situation. Some people also implement this: because we only need to synchronize when creating the class, as long as we separate the creation and getInstance() and add the synchronized keyword to the creation separately, it is also possible:
public class SingletonTest { private static SingletonTest instance = null; private SingletonTest() { } private static synchronized void syncInit() { if (instance == null) { instance = new SingletonTest(); } } public static SingletonTest getInstance() { if (instance == null) { syncInit(); } return instance; } } If you consider performance, the entire program only needs to create an instance once, so performance will not have any impact.
Supplement: The "shadow instance" method is used to synchronize the properties of singleton objects
public class SingletonTest { private static SingletonTest instance = null; private Vector properties = null; public Vector getProperties() { return properties; } private SingletonTest() { } private static synchronized void syncInit() { if (instance == null) { instance = new SingletonTest(); } } public static SingletonTest getInstance() { if (instance == null) { syncInit(); } return instance; } public void updateProperties() { SingletonTest shadow = new SingletonTest(); properties = shadow.getProperties(); } } Through learning the singleton pattern, we tell us:
1. It is simple to understand the singleton model, but it is still difficult to implement it in detail.
2. The synchronized keyword locks an object. When using it, it must be used in the appropriate place (note that the objects and processes that need to be locked, and sometimes not the entire object and the entire process need to be locked).
At this point, the singleton pattern has basically been talked about. At the end, the author suddenly thought of another question, which is to use a static class method to achieve the effect of the singleton pattern, which is also feasible. What is the difference between the two here?
First, static classes cannot implement interfaces. (It is OK from the perspective of the class, but that will destroy the static. Because there is no static modification method allowed in the interface, it is non-static even if implemented)
Secondly, singletons can be delayed initialized, and static classes are usually initialized when loaded for the first time. The reason for lazy loading is that some classes are relatively large, so lazy loading helps improve performance.
Again, the singleton class can be inherited and its methods can be overwritten. However, the internal methods of static classes are static and cannot be overwritten.
Last point, singleton classes are more flexible. After all, they are just an ordinary Java class in terms of implementation. As long as they meet the basic needs of singletons, you can implement some other functions as you wish, but static classes cannot. From the above summary, we can basically see the difference between the two. However, on the other hand, the singleton pattern we finally implemented above is implemented internally with a static class, so the two are very related, but the levels of our consideration of the problem are different. Only by combining the two ideas can a perfect solution be created. Just like HashMap uses arrays + linked lists to implement it, in fact, many things in life are like this. Using different methods to deal with problems always has advantages and disadvantages. The most perfect method is to combine the advantages of each method to solve the problem best!
4. Builder mode
The factory class model provides a pattern of creating a single class, while the builder model concentrates various products for management and uses it to create composite objects. The so-called composite object refers to a certain class having different attributes. In fact, the builder model is obtained by combining the previous abstract factory model and the final Test. Let's look at the code:
Also like the previous one, one Sender interface and two implementation classes MailSender and SmsSender. Finally, the builder class is as follows:
public class Builder { private List<Sender> list = new ArrayList<Sender>(); public void produceMailSender(int count){ for(int i=0; i<count; i++){ list.add(new MailSender()); } } public void produceSmsSender(int count){ for(int i=0; i<count; i++){ list.add(new SmsSender()); } } } Test class:
public class Test { public static void main(String[] args) { Builder builder = new Builder(); builder.produceMailSender(10); } } From this point of view, the builder pattern integrates many functions into a class, which can create more complex things. So the difference from the engineering model is that the factory model focuses on creating a single product, while the builder model focuses on creating a suitable object and multiple parts. Therefore, whether to choose the factory model or the builder model depends on the actual situation.
5. Prototype
Although the prototype pattern is a creative pattern, it has nothing to do with the engineering pattern. As you can see from the name, the idea of this pattern is to copy and clone an object as a prototype, and produce a new object similar to the original object. This summary will be explained through copying the object. In Java, copying objects is implemented through clone(), and a prototype class is created first:
public class Prototype implements Cloneable { public Object clone() throws CloneNotSupportedException { Prototype proto = (Prototype) super.clone(); return proto; } } It's very simple. A prototype class only needs to implement the Cloneable interface and overwrite the clone method. Here the clone method can be changed to any name, because the Cloneable interface is an empty interface, you can arbitrarily define the method name of the implementation class, such as cloneA or cloneB, because the focus here is the sentence super.clone(). Super.clone() calls the Object clone() method, and in the Object class, clone() is native. How to implement it specifically? I will not go into it in another article about interpreting the calls of local methods in Java. Here, I will combine the shallow copy and deep copy of objects. First of all, you need to understand the concept of deep and shallow copy of objects:
Shallow copy: After copying an object, variables of the basic data type will be recreated, while the reference type points to the original object.
Deep copy: After copying an object, both the basic data type and the reference type are recreated. Simply put, deep copying is completely copied, while shallow copying is not thorough.
Here, write an example of copying in depth:
public class Prototype implements Cloneable, Serializable { private static final long serialVersionUID = 1L; private String string; private SerializableObject obj; /* Shallow copy*/ public Object clone() throws CloneNotSupportedException { Prototype proto = (Prototype) super.clone(); return proto; } /* Deep copy*/ public Object deepClone() throws IOException, ClassNotFoundException { /* Write the binary stream to the current object*/ ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(this); /* Read out the new object generated by the binary stream*/ ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); return ois.readObject(); } public String getString() { return string; } public void setString(String string) { this.string = string; } public SerializableObject getObj() { return obj; } public void setObj(SerializableObject obj) { this.obj = obj; } } class SerializableObject implements Serializable { private static final long serialVersionUID = 1L; } To achieve deep copy, you need to read the binary input of the current object in the form of a stream, and then write out the object corresponding to the binary data.
We will continue to discuss design modes. In the previous article, I have finished talking about 5 creation modes. At the beginning of this chapter, I will talk about 7 structural modes: adapter mode, decorative mode, proxy mode, appearance mode, bridge mode, combination mode, and enjoyment mode. The adapter mode of the object is the origin of various modes. Let's look at the following figure:
The adapter pattern converts an interface of a class into another interface representation that the client expects, with the aim of eliminating the compatibility issues of the class due to interface mismatch. It is mainly divided into three categories: adapter mode of class, adapter mode of object, and adapter mode of interface. First, let’s take a look at the adapter mode of the class and look at the class diagram first:
The core idea is: There is a Source class that has a method that is to be adapted and Targetable when the target interface is Targetable. Through the Adapter class, the Source function is extended to Targetable, and read the code:
public class Source { public void method1() { System.out.println("this is original method!"); } } public interface Targetable { /* Same as the method in the original class*/ public void method1(); /* Methods of the new class*/ public void method2(); } public class Adapter extends Source implements Targetable { @Override public void method2() { System.out.println("this is the targetable method!"); } } The Adapter class inherits the Source class and implements the Targetable interface. The following is the test class:
public class AdapterTest { public static void main(String[] args) { Targetable target = new Adapter(); target.method1(); target.method2(); } } Output:
This is original method!
This is the targetable method!
In this way, the implementation class of the Targetable interface has the functions of the Source class.
Adapter mode for object
The basic idea is the same as the adapter mode of the class. It is just that the Adapter class is modified. This time, the Source class is not inherited, but the Source class is held to solve the compatibility problem. Look at the picture:
Just modify the source code of the Adapter class:
public class Wrapper implements Targetable { private Source source; public Wrapper(Source source){ super(); this.source = source; } @Override public void method2() { System.out.println("this is the targetable method!"); } @Override public void method1() { source.method1(); } } Test class:
public class AdapterTest { public static void main(String[] args) { Source source = new Source(); Targetable target = new Wrapper(source); target.method1(); target.method2(); } }The output is the same as the first one, but the adaptation method is different.
The third adapter mode is the adapter mode of the interface. The adapter of the interface is as follows: Sometimes there are multiple abstract methods in an interface we write. When we write the implementation class of the interface, we must implement all methods of the interface. This is obviously a waste, because not all methods are needed, and sometimes only some are needed. In order to solve this problem, we introduced the adapter mode of the interface. With the help of an abstract class, the abstract class implements the interface and implements all methods. We do not deal with the original interface and only get in touch with the abstract class. So we write a class, inherit the abstract class, and rewrite the methods we need. Take a look at the class diagram:
This is easy to understand. In actual development, we often encounter too many methods defined in this interface, so that sometimes we do not need them in some implementation classes. Look at the code:
public interface Sourceable { public void method1(); public void method2(); } Abstract class Wrapper2:
public abstract class Wrapper2 implements Sourceable{ public void method1(){} public void method2(){} } public class SourceSub1 extends Wrapper2 { public void method1(){ System.out.println("the sourceable interface's first Sub1!"); } }
public class SourceSub2 extends Wrapper2 { public void method2(){ System.out.println("the sourceable interface's second Sub2!"); } } public class WrapperTest { public static void main(String[] args) { Sourceable source1 = new SourceSub1(); Sourceable source2 = new SourceSub2(); source1.method1(); source1.method2(); source2.method1(); source2.method2(); } } Test output:
the sourceable interface's first Sub1!
the sourceable interface's second Sub2!
It achieved our results!
After talking about it, I will summarize the application scenarios of three adapter modes:
Class adapter mode: When you want to convert one class into a class that satisfies another new interface, you can use the class adapter mode to create a new class, inherit the original class, and implement a new interface.
Object adapter mode: When you want to convert an object into an object that satisfies another new interface, you can create a Wrapper class, holding an instance of the original class, and in the Wrapper class method, just call the instance method.
Adapter mode of interface: When you do not want to implement all methods in an interface, you can create an abstract class Wrapper to implement all methods. When we write other classes, just inherit the abstract class.
7. Decorator
As the name suggests, the decorative pattern is to add some new functions to an object, and it is dynamic, requiring the decorative object and the decorative object to implement the same interface. The decorative object holds an instance of the decorative object. The relationship diagram is as follows:
The Source class is a decorative class, and the Decorator class is a decorative class that can dynamically add some functions to the Source class. The code is as follows:
public interface Sourceable { public void method(); } public class Source implements Sourceable { @Override public void method() { System.out.println("the original method!"); } } public class Decorator implements Sourceable { private Sourceable source; public Decorator(Sourceable source){ super(); this.source = source; } @Override public void method() { System.out.println("before decorator!"); source.method(); System.out.println("after decorator!"); } } Test class:
public class DecoratorTest { public static void main(String[] args) { Sourceable source = new Source(); Sourceable obj = new Decorator(source); obj.method(); } } Output:
before decorator!
the original method!
After decorator!
Decorator mode application scenario:
1. It is necessary to extend the functions of a class.
2. Dynamically add functions to an object, and can also dynamically undo it. (Inheritance cannot do this. The inherited functions are static and cannot be added and deleted dynamically.)
Disadvantages: Too many similar objects are not easy to troubleshoot!
8. Proxy mode (Proxy)
In fact, each model name indicates the function of the model. The proxy model is to add an additional agent class to perform some operations on the original object. For example, when we are renting a house, we go back to find an agent. Why? Because you do not have a comprehensive understanding of the information about houses in the area, I hope to find someone more familiar with to help you do it. This is what the agent here means. For example, sometimes when we sue, we need to hire a lawyer because lawyers have expertise in law and can operate on our behalf and express our ideas. Let’s take a look at the relationship diagram first:
According to the above explanation, the proxy mode is easier to understand. Let's look at the code:
public interface Sourceable { public void method(); } public class Source implements Sourceable { @Override public void method() { System.out.println("the original method!"); } }
public class Proxy implements Sourceable { private Source source; public Proxy(){ super(); this.source = new Source(); } @Override public void method() { before(); source.method(); atfer(); } private void atfer() { System.out.println("after proxy!"); } private void before() { System.out.println("before proxy!"); } } Test class:
public class ProxyTest { public static void main(String[] args) { Sourceable source = new Proxy(); source.method(); } } Output:
before proxy!
the original method!
After proxy!
Application scenarios of proxy mode:
If the existing method needs to be improved when used, there are two ways:
1. Modify the original method to adapt. This violates the principle of "open to extensions and closed to modifications".
2. It is to use a proxy class to call the original method and control the generated results. This method is proxy mode.
Using the proxy mode can divide the functions more clearly, which helps with post-maintenance!
9. Appearance mode (Facade)
The appearance pattern is to solve the dependency between classes and class homes. Like spring, the relationship between classes can be configured into a configuration file. The appearance pattern is to place their relationship in a Facade class, reducing the coupling degree between class classes. There is no interface involved in this pattern. Look at the following class diagram: (We take the startup process of a computer as an example)
Let's look at the implementation class first:
public class CPU { public void startup(){ System.out.println("cpu startup!"); } public void shutdown(){ System.out.println("cpu shutdown!"); } } public class Memory { public void startup(){ System.out.println("memory startup!"); } public void shutdown(){ System.out.println("memory shutdown!"); } } public class Disk { public void startup(){ System.out.println("disk startup!"); } public void shutdown(){ System.out.println("disk shutdown!"); } } public class Computer { private CPU cpu; private Memory memory; private Disk disk; public Computer(){ cpu = new CPU(); memory = new Memory(); disk = new Disk(); } public void startup(){ System.out.println("start the computer!"); cpu.startup(); memory.startup(); disk.startup(); System.out.println("start computer finished!"); } public void shutdown(){ System.out.println("begin to close the computer!"); cpu.shutdown(); memory.shutdown(); disk.shutdown(); System.out.println("computer closed!"); } } The User class is as follows:
public class User { public static void main(String[] args) { Computer computer = new Computer(); computer.startup(); computer.shutdown(); } } Output:
start the computer!
cpu startup!
memory startup!
disk startup!
start computer finished!
begin to close the computer!
cpu shutdown!
memory shutdown!
disk shutdown!
computer closed!
If we don’t have the Computer class, then CPU, Memory, Disk will hold instances and create relationships with each other, which will cause serious dependence. Modifying one class may lead to modifications to other classes. This is not what we want to see. With the Computer class, their relationship is placed in the Computer class, which will play a decoupling role. This is the appearance pattern!
10. Bridge mode (Bridge)
桥接模式就是把事物和其具体实现分开,使他们可以各自独立的变化。桥接的用意是:将抽象化与实现化解耦,使得二者可以独立变化,像我们常用的JDBC桥DriverManager一样,JDBC进行连接数据库的时候,在各个数据库之间进行切换,基本不需要动太多的代码,甚至丝毫不用动,原因就是JDBC提供统一接口,每个数据库提供各自的实现,用一个叫做数据库驱动的程序来桥接就行了。我们来看看关系图:
Implementation code:
先定义接口:
public interface Sourceable { public void method(); }分别定义两个实现类:
public class SourceSub1 implements Sourceable { @Override public void method() { System.out.println("this is the first sub!"); } }
public class SourceSub2 implements Sourceable { @Override public void method() { System.out.println("this is the second sub!"); } }定义一个桥,持有Sourceable的一个实例:
public abstract class Bridge { private Sourceable source; public void method(){ source.method(); } public Sourceable getSource() { return source; } public void setSource(Sourceable source) { this.source = source; } } public class MyBridge extends Bridge { public void method(){ getSource().method(); } }测试类:
public class BridgeTest { public static void main(String[] args) { Bridge bridge = new MyBridge(); /*调用第一个对象*/ Sourceable source1 = new SourceSub1(); bridge.setSource(source1); bridge.method(); /*调用第二个对象*/ Sourceable source2 = new SourceSub2(); bridge.setSource(source2); bridge.method(); } } output:
this is the first sub!
this is the second sub!
这样,就通过对Bridge类的调用,实现了对接口Sourceable的实现类SourceSub1和SourceSub2的调用。接下来我再画个图,大家就应该明白了,因为这个图是我们JDBC连接的原理,有数据库学习基础的,一结合就都懂了。
11、组合模式(Composite)
组合模式有时又叫部分-整体模式在处理类似树形结构的问题时比较方便,看看关系图:
Let's take a look at the code directly:
public class TreeNode { private String name; private TreeNode parent; private Vector<TreeNode> children = new Vector<TreeNode>(); public TreeNode(String name){ this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public TreeNode getParent() { return parent; } public void setParent(TreeNode parent) { this.parent = parent; } //添加孩子节点public void add(TreeNode node){ children.add(node); } //删除孩子节点public void remove(TreeNode node){ children.remove(node); } //取得孩子节点public Enumeration<TreeNode> getChildren(){ return children.elements(); } } public class Tree { TreeNode root = null; public Tree(String name) { root = new TreeNode(name); } public static void main(String[] args) { Tree tree = new Tree("A"); TreeNode nodeB = new TreeNode("B"); TreeNode nodeC = new TreeNode("C"); nodeB.add(nodeC); tree.root.add(nodeB); System.out.println("build the tree finished!"); } }使用场景:将多个对象组合在一起进行操作,常用于表示树形结构中,例如二叉树,数等。
12、享元模式(Flyweight)
享元模式的主要目的是实现对象的共享,即共享池,当系统中对象多的时候可以减少内存的开销,通常与工厂模式一起使用。
FlyWeightFactory负责创建和管理享元单元,当一个客户端请求时,工厂需要检查当前对象池中是否有符合条件的对象,如果有,就返回已经存在的对象,如果没有,则创建一个新对象,FlyWeight是超类。一提到共享池,我们很容易联想到Java里面的JDBC连接池,想想每个连接的特点,我们不难总结出:适用于作共享的一些个对象,他们有一些共有的属性,就拿数据库连接池来说,url、driverClassName、username、password及dbname,这些属性对于每个连接来说都是一样的,所以就适合用享元模式来处理,建一个工厂类,将上述类似属性作为内部数据,其它的作为外部数据,在方法调用时,当做参数传进来,这样就节省了空间,减少了实例的数量。
看个例子:
看下数据库连接池的代码:
public class ConnectionPool { private Vector<Connection> pool; /*公有属性*/ private String url = "jdbc:mysql://localhost:3306/test"; private String username = "root"; private String password = "root"; private String driverClassName = "com.mysql.jdbc.Driver"; private int poolSize = 100; private static ConnectionPool instance = null; Connection conn = null; /*构造方法,做一些初始化工作*/ private ConnectionPool() { pool = new Vector<Connection>(poolSize); for (int i = 0; i < poolSize; i++) { try { Class.forName(driverClassName); conn = DriverManager.getConnection(url, username, password); pool.add(conn); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } } } /* 返回连接到连接池*/ public synchronized void release() { pool.add(conn); } /* 返回连接池中的一个数据库连接*/ public synchronized Connection getConnection() { if (pool.size() > 0) { Connection conn = pool.get(0); pool.remove(conn); return conn; } else { return null; } } }通过连接池的管理,实现了数据库连接的共享,不需要每一次都重新创建连接,节省了数据库重新创建的开销,提升了系统的性能!本章讲解了7种结构型模式,因为篇幅的问题,剩下的11种行为型模式,
本章是关于设计模式的最后一讲,会讲到第三种设计模式――行为型模式,共11种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。这段时间一直在写关于设计模式的东西,终于写到一半了,写博文是个很费时间的东西,因为我得为读者负责,不论是图还是代码还是表述,都希望能尽量写清楚,以便读者理解,我想不论是我还是读者,都希望看到高质量的博文出来,从我本人出发,我会一直坚持下去,不断更新,源源动力来自于读者朋友们的不断支持,我会尽自己的努力,写好每一篇文章!希望大家能不断给出意见和建议,共同打造完美的博文!
先来张图,看看这11中模式的关系:
第一类:通过父类与子类的关系进行实现。第二类:两个类之间。第三类:类的状态。第四类:通过中间类
13、策略模式(strategy)
策略模式定义了一系列算法,并将每个算法封装起来,使他们可以相互替换,且算法的变化不会影响到使用算法的客户。需要设计一个接口,为一系列实现类提供统一的方法,多个实现类实现该接口,设计一个抽象类(可有可无,属于辅助类),提供辅助函数,关系图如下:
图中ICalculator提供同意的方法,
AbstractCalculator是辅助类,提供辅助方法,接下来,依次实现下每个类:
首先统一接口:
public interface ICalculator { public int calculate(String exp); }辅助类:
public abstract class AbstractCalculator { public int[] split(String exp,String opt){ String array[] = exp.split(opt); int arrayInt[] = new int[2]; arrayInt[0] = Integer.parseInt(array[0]); arrayInt[1] = Integer.parseInt(array[1]); return arrayInt; } }三个实现类:
public class Plus extends AbstractCalculator implements ICalculator { @Override public int calculate(String exp) { int arrayInt[] = split(exp,"//+"); return arrayInt[0]+arrayInt[1]; } }
public class Minus extends AbstractCalculator implements ICalculator { @Override public int calculate(String exp) { int arrayInt[] = split(exp,"-"); return arrayInt[0]-arrayInt[1]; } }
public class Multiply extends AbstractCalculator implements ICalculator { @Override public int calculate(String exp) { int arrayInt[] = split(exp,"//*"); return arrayInt[0]*arrayInt[1]; } }简单的测试类:
public class StrategyTest { public static void main(String[] args) { String exp = "2+8"; ICalculator cal = new Plus(); int result = cal.calculate(exp); System.out.println(result); } } Output: 10
策略模式的决定权在用户,系统本身提供不同算法的实现,新增或者删除算法,对各种算法做封装。因此,策略模式多用在算法决策系统中,外部用户只需要决定用哪个算法即可。
14、模板方法模式(Template Method)
解释一下模板方法模式,就是指:一个抽象类中,有一个主方法,再定义1...n个方法,可以是抽象的,也可以是实际的方法,定义一个类,继承该抽象类,重写抽象方法,通过调用抽象类,实现对子类的调用,先看个关系图:
就是在AbstractCalculator类中定义一个主方法calculate,calculate()调用spilt()等,Plus和Minus分别继承AbstractCalculator类,通过对AbstractCalculator的调用实现对子类的调用,看下面的例子:
public abstract class AbstractCalculator { /*主方法,实现对本类其它方法的调用*/ public final int calculate(String exp,String opt){ int array[] = split(exp,opt); return calculate(array[0],array[1]); } /*被子类重写的方法*/ abstract public int calculate(int num1,int num2); public int[] split(String exp,String opt){ String array[] = exp.split(opt); int arrayInt[] = new int[2]; arrayInt[0] = Integer.parseInt(array[0]); arrayInt[1] = Integer.parseInt(array[1]); return arrayInt; } }
public class Plus extends AbstractCalculator { @Override public int calculate(int num1,int num2) { return num1 + num2; } }测试类:
public class StrategyTest { public static void main(String[] args) { String exp = "8+8"; AbstractCalculator cal = new Plus(); int result = cal.calculate(exp, "//+"); System.out.println(result); } }我跟踪下这个小程序的执行过程:首先将exp和"//+"做参数,调用AbstractCalculator类里的calculate(String,String)方法,在calculate(String,String)里调用同类的split(),之后再调用calculate(int ,int)方法,从这个方法进入到子类中,执行完return num1 + num2后,将值返回到AbstractCalculator类,赋给result,打印出来。正好验证了我们开头的思路。
15、观察者模式(Observer)
包括这个模式在内的接下来的四个模式,都是类和类之间的关系,不涉及到继承,学的时候应该记得归纳,记得本文最开始的那个图。观察者模式很好理解,类似于邮件订阅和RSS订阅,当我们浏览一些博客或wiki时,经常会看到RSS图标,就这的意思是,当你订阅了该文章,如果后续有更新,会及时通知你。其实,简单来讲就一句话:当一个对象变化时,其它依赖该对象的对象都会收到通知,并且随着变化!对象之间是一种一对多的关系。先来看看关系图:
我解释下这些类的作用:MySubject类就是我们的主对象,Observer1和Observer2是依赖于MySubject的对象,当MySubject变化时,Observer1和Observer2必然变化。AbstractSubject类中定义着需要监控的对象列表,可以对其进行修改:增加或删除被监控对象,且当MySubject变化时,负责通知在列表内存在的对象。我们看实现代码:
一个Observer接口:
public interface Observer { public void update(); }两个实现类:
public class Observer1 implements Observer { @Override public void update() { System.out.println("observer1 has received!"); } } public class Observer2 implements Observer { @Override public void update() { System.out.println("observer2 has received!"); } } Subject接口及实现类:
public interface Subject { /*增加观察者*/ public void add(Observer observer); /*删除观察者*/ public void del(Observer observer); /*通知所有的观察者*/ public void notifyObservers(); /*自身的操作*/ public void operation(); } public abstract class AbstractSubject implements Subject { private Vector<Observer> vector = new Vector<Observer>(); @Override public void add(Observer observer) { vector.add(observer); } @Override public void del(Observer observer) { vector.remove(observer); } @Override public void notifyObservers() { Enumeration<Observer> enumo = vector.elements(); while(enumo.hasMoreElements()){ enumo.nextElement().update(); } } } public class MySubject extends AbstractSubject { @Override public void operation() { System.out.println("update self!"); notifyObservers(); } }测试类:
public class ObserverTest { public static void main(String[] args) { Subject sub = new MySubject(); sub.add(new Observer1()); sub.add(new Observer2()); sub.operation(); } } Output:
update self!
observer1 has received!
observer2 has received!
这些东西,其实不难,只是有些抽象,不太容易整体理解,建议读者:根据关系图,新建项目,自己写代码(或者参考我的代码),按照总体思路走一遍,这样才能体会它的思想,理解起来容易!
16、迭代子模式(Iterator)
顾名思义,迭代器模式就是顺序访问聚集中的对象,一般来说,集合中非常常见,如果对集合类比较熟悉的话,理解本模式会十分轻松。这句话包含两层意思:一是需要遍历的对象,即聚集对象,二是迭代器对象,用于对聚集对象进行遍历访问。我们看下关系图:
这个思路和我们常用的一模一样,MyCollection中定义了集合的一些操作,MyIterator中定义了一系列迭代操作,且持有Collection实例,我们来看看实现代码:
两个接口:
public interface Collection { public Iterator iterator(); /*取得集合元素*/ public Object get(int i); /*取得集合大小*/ public int size(); } public interface Iterator { //前移public Object previous(); //后移public Object next(); public boolean hasNext(); //取得第一个元素public Object first(); }两个实现:
public class MyCollection implements Collection { public String string[] = {"A","B","C","D","E"}; @Override public Iterator iterator() { return new MyIterator(this); } @Override public Object get(int i) { return string[i]; } @Override public int size() { return string.length; } }
public class MyIterator implements Iterator { private Collection collection; private int pos = -1; public MyIterator(Collection collection){ this.collection = collection; } @Override public Object previous() { if(pos > 0){ pos--; } return collection.get(pos); } @Override public Object next() { if(pos<collection.size()-1){ pos++; } return collection.get(pos); } @Override public boolean hasNext() { if(pos<collection.size()-1){ return true; }else{ return false; } } @Override public Object first() { pos = 0; return collection.get(pos); } }测试类:
public class Test { public static void main(String[] args) { Collection collection = new MyCollection(); Iterator it = collection.iterator(); while(it.hasNext()){ System.out.println(it.next()); } } }输出:ABCDE
此处我们貌似模拟了一个集合类的过程,感觉是不是很爽?其实JDK中各个类也都是这些基本的东西,加一些设计模式,再加一些优化放到一起的,只要我们把这些东西学会了,掌握好了,我们也可以写出自己的集合类,甚至框架!
17、责任链模式(Chain of Responsibility)
接下来我们将要谈谈责任链模式,有多个对象,每个对象持有对下一个对象的引用,这样就会形成一条链,请求在这条链上传递,直到某一对象决定处理该请求。但是发出者并不清楚到底最终那个对象会处理该请求,所以,责任链模式可以实现,在隐瞒客户端的情况下,对系统进行动态的调整。先看看关系图:
Abstracthandler类提供了get和set方法,方便MyHandle类设置和修改引用对象,MyHandle类是核心,实例化后生成一系列相互持有的对象,构成一条链。
public interface Handler { public void operator(); }
public abstract class AbstractHandler { private Handler handler; public Handler getHandler() { return handler; } public void setHandler(Handler handler) { this.handler = handler; } }
public class MyHandler extends AbstractHandler implements Handler { private String name; public MyHandler(String name) { this.name = name; } @Override public void operator() { System.out.println(name+"deal!"); if(getHandler()!=null){ getHandler().operator(); } } } public class Test { public static void main(String[] args) { MyHandler h1 = new MyHandler("h1"); MyHandler h2 = new MyHandler("h2"); MyHandler h3 = new MyHandler("h3"); h1.setHandler(h2); h2.setHandler(h3); h1.operator(); } } Output:
h1deal!
h2deal!
h3deal!
此处强调一点就是,链接上的请求可以是一条链,可以是一个树,还可以是一个环,模式本身不约束这个,需要我们自己去实现,同时,在一个时刻,命令只允许由一个对象传给另一个对象,而不允许传给多个对象。
18、命令模式(Command)
命令模式很好理解,举个例子,司令员下令让士兵去干件事情,从整个事情的角度来考虑,司令员的作用是,发出口令,口令经过传递,传到了士兵耳朵里,士兵去执行。这个过程好在,三者相互解耦,任何一方都不用去依赖其他人,只需要做好自己的事儿就行,司令员要的是结果,不会去关注到底士兵是怎么实现的。我们看看关系图:
Invoker是调用者(司令员),Receiver是被调用者(士兵),MyCommand是命令,实现了Command接口,持有接收对象,看实现代码:
public interface Command { public void exe(); }
public class MyCommand implements Command { private Receiver receiver; public MyCommand(Receiver receiver) { this.receiver = receiver; } @Override public void exe() { receiver.action(); } }
public class Receiver { public void action(){ System.out.println("command received!"); } }
public class Invoker { private Command command; public Invoker(Command command) { this.command = command; } public void action(){ command.exe(); } }
public class Test { public static void main(String[] args) { Receiver receiver = new Receiver(); Command cmd = new MyCommand(receiver); Invoker invoker = new Invoker(cmd); invoker.action(); } }输出:command received!
这个很哈理解,命令模式的目的就是达到命令的发出者和执行者之间解耦,实现请求和执行分开,熟悉Struts的同学应该知道,Struts其实就是一种将请求和呈现分离的技术,其中必然涉及命令模式的思想!
其实每个设计模式都是很重要的一种思想,看上去很熟,其实是因为我们在学到的东西中都有涉及,尽管有时我们并不知道,其实在Java本身的设计之中处处都有体现,像AWT、JDBC、集合类、IO管道或者是Web框架,里面设计模式无处不在。因为我们篇幅有限,很难讲每一个设计模式都讲的很详细,不过我会尽我所能,尽量在有限的空间和篇幅内,把意思写清楚了,更好让大家明白。本章不出意外的话,应该是设计模式最后一讲了,首先还是上一下上篇开头的那个图:
本章讲讲第三类和第四类。
19、备忘录模式(Memento)
主要目的是保存一个对象的某个状态,以便在适当的时候恢复对象,个人觉得叫备份模式更形象些,通俗的讲下:假设有原始类A,A中有各种属性,A可以决定需要备份的属性,备忘录类B是用来存储A的一些内部状态,类C呢,就是一个用来存储备忘录的,且只能存储,不能修改等操作。做个图来分析一下:
Original类是原始类,里面有需要保存的属性value及创建一个备忘录类,用来保存value值。Memento类是备忘录类,Storage类是存储备忘录的类,持有Memento类的实例,该模式很好理解。直接看源码:
public class Original { private String value; public String getValue() { return value; } public void setValue(String value) { this.value = value; } public Original(String value) { this.value = value; } public Memento createMemento(){ return new Memento(value); } public void restoreMemento(Memento memento){ this.value = memento.getValue(); } }
public class Memento { private String value; public Memento(String value) { this.value = value; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } } public class Storage { private Memento memento; public Storage(Memento memento) { this.memento = memento; } public Memento getMemento() { return memento; } public void setMemento(Memento memento) { this.memento = memento; } }测试类:
public class Test { public static void main(String[] args) { // 创建原始类Original origi = new Original("egg"); // 创建备忘录Storage storage = new Storage(origi.createMemento()); // 修改原始类的状态System.out.println("初始化状态为:" + origi.getValue()); origi.setValue("niu"); System.out.println("修改后的状态为:" + origi.getValue()); // 回复原始类的状态origi.restoreMemento(storage.getMemento()); System.out.println("恢复后的状态为:" + origi.getValue()); } } Output:
初始化状态为:egg
修改后的状态为:niu
恢复后的状态为:egg
简单描述下:新建原始类时,value被初始化为egg,后经过修改,将value的值置为niu,最后倒数第二行进行恢复状态,结果成功恢复了。其实我觉得这个模式叫“备份-恢复”模式最形象。
20、状态模式(State)
核心思想就是:当对象的状态改变时,同时改变其行为,很好理解!就拿QQ来说,有几种状态,在线、隐身、忙碌等,每个状态对应不同的操作,而且你的好友也能看到你的状态,所以,状态模式就两点:1、可以通过改变状态来获得不同的行为。2、你的好友能同时看到你的变化。看图:
State类是个状态类,Context类可以实现切换,我们来看看代码:
package com.xtfggef.dp.state; /** * 状态类的核心类* 2012-12-1 * @author erqing * */ public class State { private String value; public String getValue() { return value; } public void setValue(String value) { this.value = value; } public void method1(){ System.out.println("execute the first opt!"); } public void method2(){ System.out.println("execute the second opt!"); } } package com.xtfggef.dp.state; /** * 状态模式的切换类2012-12-1 * @author erqing * */ public class Context { private State state; public Context(State state) { this.state = state; } public State getState() { return state; } public void setState(State state) { this.state = state; } public void method() { if (state.getValue().equals("state1")) { state.method1(); } else if (state.getValue().equals("state2")) { state.method2(); } } }测试类:
public class Test { public static void main(String[] args) { State state = new State(); Context context = new Context(state); //设置第一种状态state.setValue("state1"); context.method(); //设置第二种状态state.setValue("state2"); context.method(); } } Output:
execute the first opt!
execute the second opt!
根据这个特性,状态模式在日常开发中用的挺多的,尤其是做网站的时候,我们有时希望根据对象的某一属性,区别开他们的一些功能,比如说简单的权限控制等。
21、访问者模式(Visitor)
访问者模式把数据结构和作用于结构上的操作解耦合,使得操作集合可相对自由地演化。访问者模式适用于数据结构相对稳定算法又易变化的系统。因为访问者模式使得算法操作增加变得容易。若系统数据结构对象易于变化,经常有新的数据对象增加进来,则不适合使用访问者模式。访问者模式的优点是增加操作很容易,因为增加操作意味着增加新的访问者。访问者模式将有关行为集中到一个访问者对象中,其改变不影响系统数据结构。其缺点就是增加新的数据结构很困难。―― From 百科简单来说,访问者模式就是一种分离对象数据结构与行为的方法,通过这种分离,可达到为一个被访问者动态添加新的操作而无需做其它的修改的效果。简单关系图:
来看看原码:一个Visitor类,存放要访问的对象,
public interface Visitor { public void visit(Subject sub); }
public class MyVisitor implements Visitor { @Override public void visit(Subject sub) { System.out.println("visit the subject:"+sub.getSubject()); } } Subject类,accept方法,接受将要访问它的对象,getSubject()获取将要被访问的属性,
public interface Subject { public void accept(Visitor visitor); public String getSubject(); } public class MySubject implements Subject { @Override public void accept(Visitor visitor) { visitor.visit(this); } @Override public String getSubject() { return "love"; } } test:
public class Test { public static void main(String[] args) { Visitor visitor = new MyVisitor(); Subject sub = new MySubject(); sub.accept(visitor); } }输出:visit the subject:love
该模式适用场景:如果我们想为一个现有的类增加新功能,不得不考虑几个事情:1、新功能会不会与现有功能出现兼容性问题?2、以后会不会再需要添加?3、如果类不允许修改代码怎么办?面对这些问题,最好的解决方法就是使用访问者模式,访问者模式适用于数据结构相对稳定的系统,把数据结构和算法解耦,
22、中介者模式(Mediator)
中介者模式也是用来降低类类之间的耦合的,因为如果类类之间有依赖关系的话,不利于功能的拓展和维护,因为只要修改一个对象,其它关联的对象都得进行修改。如果使用中介者模式,只需关心和Mediator类的关系,具体类类之间的关系及调度交给Mediator就行,这有点像spring容器的作用。先看看图:
User类统一接口,User1和User2分别是不同的对象,二者之间有关联,如果不采用中介者模式,则需要二者相互持有引用,这样二者的耦合度很高,为了解耦,引入了Mediator类,提供统一接口,
MyMediator为其实现类,里面持有User1和User2的实例,用来实现对User1和User2的控制。这样User1和User2两个对象相互独立,他们只需要保持好和Mediator之间的关系就行,剩下的全由MyMediator类来维护!基本实现:
public interface Mediator { public void createMediator(); public void workAll(); }
public class MyMediator implements Mediator { private User user1; private User user2; public User getUser1() { return user1; } public User getUser2() { return user2; } @Override public void createMediator() { user1 = new User1(this); user2 = new User2(this); } @Override public void workAll() { user1.work(); user2.work(); } } public abstract class User { private Mediator mediator; public Mediator getMediator(){ return mediator; } public User(Mediator mediator) { this.mediator = mediator; } public abstract void work(); }
public class User1 extends User { public User1(Mediator mediator){ super(mediator); } @Override public void work() { System.out.println("user1 exe!"); } } [java] view plaincopypublic class User2 extends User { public User2(Mediator mediator){ super(mediator); } @Override public void work() { System.out.println("user2 exe!"); } }测试类:
public class Test { public static void main(String[] args) { Mediator mediator = new MyMediator(); mediator.createMediator(); mediator.workAll(); } } Output:
user1 exe!
user2 exe!
23、解释器模式(Interpreter)
解释器模式是我们暂时的最后一讲,一般主要应用在OOP开发中的编译器的开发中,所以适用面比较窄。
Context类是一个上下文环境类,Plus和Minus分别是用来计算的实现,代码如下:
public interface Expression { public int interpret(Context context); } public class Plus implements Expression { @Override public int interpret(Context context) { return context.getNum1()+context.getNum2(); } } public class Minus implements Expression { @Override public int interpret(Context context) { return context.getNum1()-context.getNum2(); } }
public class Context { private int num1; private int num2; public Context(int num1, int num2) { this.num1 = num1; this.num2 = num2; } public int getNum1() { return num1; } public void setNum1(int num1) { this.num1 = num1; } public int getNum2() { return num2; } public void setNum2(int num2) { this.num2 = num2; } } public class Test { public static void main(String[] args) { // 计算9+2-8的值int result = new Minus().interpret((new Context(new Plus() .interpret(new Context(9, 2)), 8))); System.out.println(result); } }最后输出正确的结果:3。
基本就这样,解释器模式用来做各种各样的解释器,如正则表达式等的解释器等等!
设计模式基本就这么大概讲完了,总体感觉有点简略,的确,这么点儿篇幅,不足以对整个23种设计模式做全面的阐述,此处读者可将它作为一个理论基础去学习,通过这四篇博文,先基本有个概念,虽然我讲的有些简单,但基本都能说明问题及他们的特点,如果对哪一个感兴趣,可以继续深入研究!同时我也会不断更新,尽量补全遗漏、修正不足,欢迎广大读者及时提出好的建议,我们一起学习!项目中涉及到的代码,已经放到了我的资源里:http://download.csdn.net/detail/zhangerqing/4835830(因为我不喜欢不劳而获,所以没有免积分,只设置了5个,如果有人实在没积分又急要,那么联系我吧,我给你发过去)。