The proxy pattern

Add functionality (non-functional requirements, such as monitoring, statistics, transactions, logging, etc.) to the original class by introducing a proxy class without changing the code of the original class (or proxyed class).

Static proxy (original class maintainable – unified interface can be implemented)

public interface IDownload{
		void startDownload(a);
		void stopDownload(a);
}

public class DownloadImpl implements IDownload{
		void startDownload(a){
				//....
		}
		
		void stopDownload(a){
				//....}}public class DownloadProxy implements IDownload{
		private MonitorLog mLog;
		private DownloadImpl mDownloadController;
		
		public DownloadLog(DownloadImpl downloadController){
				mLog = new MonitorLog();
				mDownloadController = downloadController;
		}
		
		public void startDownload(a){
				mLog.info(TAG, "xxxx");
				mDownloadController.startDownload();
		}
		
		public void stopDownload(a){
				mLog.info(TAG, "xxxxx"); mDownloadController.stopDownload(); }}public class RunClass{
		public static void main(String[] args){
				IDownload downloadController = new DownloadProxy(newDownloadImpl); download.startDownload(); }}Copy the code

Static proxies (primitive classes are not maintainable – unified interfaces are not possible)

// This class is a third-party library class
public class DownloadManager implements IDownload{
		void startDownload(a){... }void stopDownload(a){... }}public class DownloadProxy extends DownloadManager{
		private MonitorLog mLog;
		
		public DownloadLog(a){
				mLog = new MonitorLog();
		}
		
		public void startDownload(a){
				mLog.info(TAG, "xxxx");
				super.startDownload();
		}
		
		public void stopDownload(a){
				mLog.info(TAG, "xxxxx");
				super.stopDownload(); }}public class RunClass{
		public static void main(String[] args){
				DownloadManager downloadController = newDownloadProxy(); download.startDownload(); }}Copy the code

A dynamic proxy

Instead of writing a proxy class for each primitive class in advance, you dynamically create a proxy class for the primitive class at runtime and replace the primitive class with a proxy class in the system.

Example scenario: Cache function of the interface. You can provide a cached annotation, and all interface calls are made through the dynamic proxy class. The dynamic proxy class resolves whether there are cached annotations, and returns cached content if there are any, and real-time content if not.

public class DynamicProxy {
  private MonitorLog mLog;

  public DynamicProxy(a) {
    	mLog = new MonitorLog();
  }

  public Object createProxy(Object proxiedObject) { Class<? >[] interfaces = proxiedObject.getClass().getInterfaces(); DynamicProxyHandler handler =new DynamicProxyHandler(proxiedObject);
    return Proxy.newProxyInstance(proxiedObject.getClass().getClassLoader(), interfaces, handler);
  }

  private class DynamicProxyHandler implements InvocationHandler {
    private Object proxiedObject;

    public DynamicProxyHandler(Object proxiedObject) {
      this.proxiedObject = proxiedObject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      mLog.info(TAG, "Currently calling method name =" + method.getName());
      Object result = method.invoke(proxiedObject, args);
      returnresult; }}}public class RunClass{
  public static void main(String[] args){
    DynamicProxy proxy = new DynamicProxy();
		IDownload downloadController = (IDownload) proxy.createProxy(newDownloadManager()); downloadController.startDownload(); }}Copy the code

The bridge model

Decouple abstractions and implementations so that they can vary independently. Another way to think about it: “A class has two (or more) dimensions that vary independently, and we combine them so that these two (or more) dimensions can scale independently.” To avoid exponential explosion of inheritance hierarchy, combinatorial relation is used to replace inheritance relation.

MySql implements the JDBC driver protocol
package com.mysql.jdbc;
import java.sql.SQLException;

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
  static {
    try {
      java.sql.DriverManager.registerDriver(new Driver());
    } catch (SQLException E) {
      throw new RuntimeException("Can't register driver!"); }}/**
   * Construct a new driver and register it with DriverManager
   * @throws SQLException if a database error occurs.
   */
  public Driver(a) throws SQLException {
    // Required for Class.forName().newInstance()}}// Load the MySql driver with class.forname and execute the static block to register the MySql driver
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/sample_db? user=root&password=your_password"; Connection con = DriverManager.getConnection(url); Statement STMT = con.createstatement (); String query ="select * from test";
ResultSet rs=stmt.executeQuery(query);
while(rs.next()) {
  rs.getString(1);
  rs.getInt(2);
}
Copy the code

For example: According to the inherited design pattern, Car is an Abstract base class, assuming that there are M Car brands, N gears we’re going to write M by N classes to describe all the combinations of cars and gears. When we use bridge mode, I first new a specific Car (such as Mercedes), and then new a specific Transmission (such as automatic). And then the Mercedes. Set (manual) will do. So this schema only has M+N classes that can describe all types, so that’s an M by N explosion of inherited classes reduced to an M+N combination. So the bridge pattern should solve the inheritance explosion problem.

Decorator pattern

Decorator pattern mainly solves the problem that inheritance relationship is too complicated, and replaces inheritance by composition. Its main function is to add enhancements to the original class. This is also an important basis for deciding whether to use the decorator pattern. In addition, the decorator pattern is characterized by the ability to nest multiple decorators for primitive classes. To satisfy this application scenario, the decorator class needs to inherit the same abstract class or interface as the original class when it is designed.

public abstract class InputStream {
  / /...
  public int read(byte b[]) throws IOException {
    return read(b, 0, b.length);
  }
  
  public int read(byte b[], int off, int len) throws IOException {
    / /...
  }
	/ /...
}

public class BufferedInputStream extends InputStream {
  protected volatile InputStream in;

  protected BufferedInputStream(InputStream in) {
    this.in = in;
  }
  
  / /... Implement cache-based read data interface...
  public int read(byte b[]) throws IOException {
    // Implement caching
    return in.read(b, 0, b.length); }}public class DataInputStream extends InputStream {
  protected volatile InputStream in;

  protected DataInputStream(InputStream in) {
    this.in = in;
  }
  
  / /... Implement an interface for reading basic types of data
}


public class RunClass{
  public static void main(String[] args){
    InputStream in = new FileInputStream("/user/test/test.txt");
    InputStream bin = new BufferedInputStream(in);
    byte[] data = new byte[128];
    while(bin.read(data) ! = -1) {
      / /...}}}Copy the code

Adapter mode

Class adapter (based on inheritance)

public interface ITarget {
  void f1(a);
  void f2(a);
  void fc(a);
}

public class Adaptee {
  public void fa(a) { / /... }
  public void fb(a) { / /... }
  public void fc(a) { / /... }
}

public class Adaptor extends Adaptee implements ITarget {
  public void f1(a) {
    super.fa();
  }
  
  public void f2(a) {
    / /... Re-implement F2 ()...
  }
  
  // Fc () does not need to be implemented, but inherits directly from Adaptee, which is the biggest difference from object adapters
}
Copy the code

Object adapter: Based on composition

public interface ITarget {
  void f1(a);
  void f2(a);
  void fc(a);
}

public class Adaptee {
  public void fa(a) { / /... }
  public void fb(a) { / /... }
  public void fc(a) { / /... }
}

public class Adaptor implements ITarget {
  private Adaptee adaptee;
  
  public Adaptor(Adaptee adaptee) {
    this.adaptee = adaptee;
  }
  
  public void f1(a) {
    adaptee.fa(); // Delegate to Adaptee
  }
  
  public void f2(a) {
    / /... Re-implement F2 ()...
  }
  
  public void fc(a) { adaptee.fc(); }}Copy the code

With these two implementations, how do you choose which one to use in real development? There are two criteria for judging: one is the number of Adaptee interfaces and the other is the degree of compatibility between Adaptee and ITarget.

  • If there are not many Adaptee interfaces, either implementation will work.
  • If there are many Adaptee interfaces, and most of the Adaptee and ITarget interface definitions are the same, then we recommend using a class adapter, because Adaptor reuse the parent Adaptee interface, compared to the object adapter implementation, the amount of Adaptor code is less.
  • If there are many Adaptee interfaces and most of the Adaptee and ITarget interface definitions are different, object adapters are recommended because composite structures are more flexible than inheritance.

Application scenarios

Encapsulate a flawed interface design

Let’s say the external system we rely on has a flaw in interface design (such as a large number of static methods) that, when introduced, affects the testability of our own code. In order to isolate the design defects, we hope to perform secondary encapsulation of the interface provided by the external system, and abstract out a better interface design.

public class CD { // This class comes from an external SDK and we have no right to modify its code
  / /...
  public static void staticFunction1(a) { / /... }
  
  public void uglyNamingFunction2(a) { / /... }

  public void tooManyParamsFunction3(int paramA, int paramB, ...) { / /... }
  
   public void lowPerformanceFunction4(a) { / /... }
}

// Refactor using the adapter pattern
public class ITarget {
  void function1(a);
  void function2(a);
  void fucntion3(ParamsWrapperDefinition paramsWrapper);
  void function4(a);
  / /...
}
// Note that the name of an adapter class does not have to end with an Adaptor
public class CDAdaptor extends CD implements ITarget {
  / /...
  public void function1(a) {
     super.staticFunction1();
  }
  
  public void function2(a) {
    super.uglyNamingFucntion2();
  }
  
  public void function3(ParamsWrapperDefinition paramsWrapper) {
     super.tooManyParamsFunction3(paramsWrapper.getParamA(), ...) ; }public void function4(a) {
    / /... reimplement it...}}Copy the code

Unify the interface design of multiple classes

The implementation of a function depends on multiple external systems (or classes). By adapting their interfaces to a uniform interface definition through the adapter pattern, we can then reuse code logic using the polymorphic nature.

/** * integrate multiple sensitive word system, provide a unified external interface */
public class ASensitiveWordsFilter { // A Interface provided by the sensitive word filtering system
  //text is the original text, and the function outputs the text after the sensitive word replaced by ***
  public String filterSexyWords(String text) {
    // ...
  }
  
  public String filterPoliticalWords(String text) {
    // ...}}public class BSensitiveWordsFilter  { // B Interface provided by the sensitive word filtering system
  public String filter(String text) {
    / /...}}public class CSensitiveWordsFilter { // The interface provided by the C sensitive word filtering system
  public String filter(String text, String mask) {
    / /...}}// Code before using the adapter pattern: code is not testability, scalability poor
public class RiskManagement {
  private ASensitiveWordsFilter aFilter = new ASensitiveWordsFilter();
  private BSensitiveWordsFilter bFilter = new BSensitiveWordsFilter();
  private CSensitiveWordsFilter cFilter = new CSensitiveWordsFilter();
  
  public String filterSensitiveWords(String text) {
    String maskedText = aFilter.filterSexyWords(text);
    maskedText = aFilter.filterPoliticalWords(maskedText);
    maskedText = bFilter.filter(maskedText);
    maskedText = cFilter.filter(maskedText, "* * *");
    returnmaskedText; }}// Use the adapter pattern for transformation
public interface ISensitiveWordsFilter { // Unify the interface definition
  String filter(String text);
}

public class ASensitiveWordsFilterAdaptor implements ISensitiveWordsFilter {
  private ASensitiveWordsFilter aFilter;
  public String filter(String text) {
    String maskedText = aFilter.filterSexyWords(text);
    maskedText = aFilter.filterPoliticalWords(maskedText);
    returnmaskedText; }}/ /... Omit BSensitiveWordsFilterAdaptor, CSensitiveWordsFilterAdaptor...

// If you add a new sensitive word filter system,
// This class doesn't need to be changed at all; And based on interface rather than implementation programming, the code is more testable.
public class RiskManagement { 
  private List<ISensitiveWordsFilter> filters = new ArrayList<>();
 
  public void addSensitiveWordsFilter(ISensitiveWordsFilter filter) {
    filters.add(filter);
  }
  
  public String filterSensitiveWords(String text) {
    String maskedText = text;
    for (ISensitiveWordsFilter filter : filters) {
      maskedText = filter.filter(maskedText);
    }
    returnmaskedText; }}Copy the code

Replace dependent external systems

Using the adapter pattern can reduce code changes when replacing one external system you depend on in your project with another.

// External system A
public interface IA {
  / /...
  void fa(a);
}
public class A implements IA {
  / /...
  public void fa(a) { / /... }
}
// An example of the use of external system A in our project
public class Demo {
  private IA a;
  public Demo(IA a) {
    this.a = a;
  }
  / /...
}
Demo d = new Demo(new A());

// Replace external system A with external system B
public class BAdaptor implemnts IA {
  private B b;
  public BAdaptor(B b) {
    this.b= b;
  }
  public void fa(a) {
    / /...b.fb(); }}// With the help of BAdaptor, the Demo code calls the IA interface place does not change,
// Just inject BAdaptor into Demo as follows.
Demo d = new Demo(new BAdaptor(new B()));
Copy the code

Compatible with older interfaces

During the version upgrade, we did not delete some interfaces to be deprecated, but retained them temporarily and marked them as deprecated, and delegated the internal implementation logic to the new interface implementation. This has the advantage of giving projects that use it a transition period rather than forcing code changes. This can also be seen roughly as an application scenario for the adapter pattern.

//JDK2.0 Collections are compatible with JDK1.0 Enumeration
public class Collections {
  public static Emueration emumeration(final Collection c) {
    return new Enumeration() {
      Iterator i = c.iterator();
      
      public boolean hasMoreElments(a) {
        return i.hashNext();
      }
      
      public Object nextElement(a) {
        return i.next():
      }
    }
  }
}
Copy the code

Adapt to different formats of data

List<String> stooges = Arrays.asList("Larry"."Moe"."Curly");
Copy the code

4. Differences in design patterns

Proxy mode: The proxy mode defines a proxy class for the original class without changing the interface of the original class. The primary purpose of the proxy mode is to control access, rather than enhance functionality, which is the biggest difference from the decorator mode.

Bridge pattern: The purpose of the bridge pattern is to separate the interface part from the implementation part so that they can be changed relatively easily and independently.

Decorator pattern: The Decorator pattern enhances the functionality of the original class without changing the interface of the original class and supports nested use of multiple decorators.

Adapter pattern: The adapter pattern is an afterthought strategy. The adapter provides a different interface from the original class, whereas the proxy and decorator patterns provide the same interface as the original class.