Java Design Patterns - adapter pattern, bridge pattern, decorator pattern

Posted by mephistoo on Mon, 10 Jan 2022 17:51:43 +0100

Chapter 9 adapter mode

9.1 basic information

  1. Basic introduction
    (1) Adapter pattern converts the interface of a class into another interface expected by the client. The main purpose is compatibility, so that two classes that cannot work together due to interface mismatch can work together. Its alias is wrapper
    (2) The adapter mode is a structural mode
    (3) It is mainly divided into three categories: class adapter mode, object adapter mode and interface adapter mode
  2. working principle
    1 (1) adapter mode: convert the interface of one class to another Make classes that are incompatible with the original interface compatible
    (2) From the user's point of view, the Adaptee is decoupled. The user receives the feedback result and feels that he is only interacting with the target interface
    (3) The user calls the target interface method transformed by the adapter, and the adapter then calls the relevant interface method of the Adaptee. As shown in the figure

Type 9.2 adapter

9.2.1 class of adapter mode

  1. Basic introduction: the Adapter class inherits the src class, implements the dst class interface, and completes the adaptation of src - > dst.
  2. Class diagram

9.2.2 application cases

  1. Use case
    Take the charger as an example to explain the Adapter. The charger itself is equivalent to the Adapter, 220V AC is equivalent to src (i.e. the Adapter), and our target DST (i.e. the target) is 5V DC.
  2. code
    (1) Adapted class
//Adapted class
public class Voltage220V {
	//Output 220V voltage
	public int output220V() {
		int src = 220;
		System.out.println("Voltage=" + src + "Crouch");
		return src;
	}
}

(2) Adapter interface

//Adapter interface
public interface IVoltage5V {
	public int output5V();
}

(3) Adapter class

//adapter class 
public class VoltageAdapter extends Voltage220V implements IVoltage5V {
	@Override
	public int output5V() {
		//220V voltage obtained
		int srcV = output220V();
		int dstV = srcV / 44 ; //Convert to 5v
		return dstV;
	}
}

(4) Mobile phone class

public class Phone {
	//charge
	public void charging(IVoltage5V iVoltage5V) {
		if(iVoltage5V.output5V() == 5) {
			System.out.println("The voltage is 5 V, Can charge~~");
		} else if (iVoltage5V.output5V() > 5) {
			System.out.println("Voltage greater than 5 V, Can't charge~~");
		}
	}
}

(5) Client

public class Client {
	public static void main(String[] args) {
		System.out.println(" === Class adapter mode ====");
		Phone phone = new Phone();
		//Because it is inheritance, you do not need to pass in parameters
		phone.charging(new VoltageAdapter());
	}
}

9.2.2 precautions for type 2 Adapter

  1. Java is a single inheritance mechanism, so the class adapter needs to inherit src class, which is a disadvantage, because it requires dst to be an interface, which has certain limitations;
  2. The methods of src class will be exposed in the Adapter, which also increases the cost of use.
  3. Because it inherits the src class, it can rewrite the methods of the src class according to requirements, which enhances the flexibility of the Adapter.

9.3 object adapter

9.3.1 basic introduction

  1. The basic idea is the same as the Adapter mode of the class, except that the Adapter class is modified instead of inheriting the src class, but holding an instance of the src class to solve the compatibility problem. That is, hold src class, implement dst class interface, and complete src - > dst adaptation
  2. According to the "composite Reuse Principle", try to use association relationship (aggregation) in the system to replace inheritance relationship.
  3. The object adapter pattern is a common pattern of adapter patterns

9.3.2 application cases

  1. The scene is the same as above.
  2. Class diagram
  3. code implementation
    (1) The adapted class, adapted interface and Phone class remain unchanged
    (2) Adapter class
    Object passing is implemented through the constructor. This object is passed in from the Client, not through the output220 method in voltage220.
//adapter class 
public class VoltageAdapter  implements IVoltage5V {
	private Voltage220V voltage220V; // Association - aggregation
	//Pass in a Voltage220V instance through the constructor;
	public VoltageAdapter(Voltage220V voltage220v) {
		this.voltage220V = voltage220v;
	}
	@Override
	public int output5V() {
		int dst = 0;
		if(null != voltage220V) {
			int src = voltage220V.output220V();//Obtain 220V voltage
			System.out.println("Use the object adapter for adaptation~~");
			dst = src / 44;
			System.out.println("After adaptation, the output voltage is=" + dst);
		}		
		return dst;		
	}
}

(3) Client class

public class Client {
	public static void main(String[] args) {
		System.out.println(" === Object Adapter Pattern  ====");
		Phone phone = new Phone();
		//Because it is an aggregation relationship, you need to pass in parameters
		phone.charging(new VoltageAdapter(new Voltage220V()));
	}
}

9.3.3 precautions for object adapter

  1. Object adapter and class adapter are actually the same idea, but they are implemented in different ways.
    According to the principle of composite reuse, composition is used to replace inheritance, so it solves the limitation that class adapters must inherit src, and it no longer requires dst to be an interface.
  2. Lower cost and more flexible.

9.4 interface adapter (default adapter)

9.4.1 basic introduction

  1. Core idea: when you do not need to implement all the methods provided by the interface, you can first design an abstract class to implement the interface and provide a default implementation (empty method) for each method in the interface. Then the subclass of the abstract class can selectively override some methods of the parent class to meet the requirements
  2. This applies when an interface does not want to use all its methods.

9.4.2 application cases

  1. Class diagram
  2. code implementation
    (1) Interface
public interface Interface4 {
	public void m1();
	public void m2();
	public void m3();
	public void m4();
}

(2) An empty class that implements the interface

//In AbsAdapter, we implement the method of Interface4 by default
public abstract class AbsAdapter implements Interface4 {
	//Default implementation
	public void m1() {	}
	public void m2() {	}
	public void m3() {	}
	public void m4() {	}
}

(3) Selectively implement the classes you need

public class Client {
	public static void main(String[] args) {	
		AbsAdapter absAdapter = new AbsAdapter() {
			//We just need to override. We need to use interface methods
			@Override
			public void m1() {
				// TODO Auto-generated method stub
				System.out.println("Used m1 Method of");
			}
		};
		absAdapter.m1();
	}
}

9.5 precautions and details of adapter mode

  1. The three naming methods are named according to the form in which src is given to the Adapter (the form in the Adapter).
  2. Class Adapter: given by class, in the Adapter, src is regarded as a class and inherited
    Object Adapter: the object is given. In the Adapter, src is taken as an object and held
    Interface Adapter: the interface is given to. In the Adapter, src is used as an interface to implement
  3. The biggest function of the Adapter pattern is to integrate incompatible interfaces.
  4. In the actual development, the implementation is not limited to the three classic forms we explain

Chapter 10 bridging mode

10.1 mobile phone operation problems

  1. Now, the operation programming is implemented for different brands of different mobile phone types (such as turning on, turning off, surfing the Internet, making calls, etc.), as shown in the figure:
  2. Traditional solutions to mobile phone operation problems
    Class diagram corresponding to traditional methods
  3. Analysis of traditional solutions to mobile phone operation problems
    (1) Scalability problem (class explosion). If we add more mobile phone styles (rotary), we need to add mobile phone classes of various brands. Similarly, if we add a mobile phone brand, we also need to add them under each mobile phone style class.
    (2) In violation of the principle of single responsibility, when we add mobile phone styles, we should add all brands of mobile phones at the same time, which increases the cost of code maintenance
    (3) Solution - use bridge mode

10.2 bridging mode

  1. Basic introduction
    (1) Bridge pattern refers to putting the implementation and abstraction in two different class levels, so that the two levels can be changed independently.
    (2) It is a structural design mode
    (3) Bridge pattern is based on the minimum design principle of classes. Different classes assume different responsibilities by using encapsulation, aggregation and inheritance. Its main feature is to separate abstraction from implementation, so as to maintain the independence of each part and deal with their functional expansion
  2. Class diagram

    explain
    (1) Client class: caller of bridge mode
    (2) Abstract class: it maintains the implementer / its implementation class, ConcreteImplementorA..., which is an aggregation relationship, and abstract acts as a bridge class
    (3) RefinedAbstraction: is a subclass of the Abstraction abstract class
    (4) Implementor: the interface of the behavior implementation class
    (5) ConcreteImplementorA /B: concrete implementation class of behavior
  3. From UML diagram: the abstract classes and interfaces here are aggregate relationships, in fact, the calling and called relationships

10.3 bridging mode to solve mobile phone operation problems

  1. Class diagram
  2. code implementation
    (1) brand interface
//Interface
public interface Brand {
	void open();
	void close();
	void call();
}

(2) Mobile phone implementation class, which implements brand interface

public class XiaoMi implements Brand {
	@Override
	public void open() {
		System.out.println(" Millet phone on ");
	}
	@Override
	public void close() {
		System.out.println(" Xiaomi mobile phone off ");
	}
	@Override
	public void call() {
		System.out.println(" Millet phone call ");
	}
}

(3) Abstract mobile phone class

public abstract class Phone {
	//Combined brand
	private Brand brand;
	//constructor 
	public Phone(Brand brand) {
		super();
		this.brand = brand;
	}
	protected void open() {
		this.brand.open();
	}
	protected void close() {
		brand.close();
	}
	protected void call() {
		brand.call();
	}
}

(4) Concrete implementation of abstract classes

//Foldable mobile Phone class, inheriting abstract class Phone
public class FoldedPhone extends Phone {
	//constructor 
	public FoldedPhone(Brand brand) {
		super(brand);
	}
	public void open() {
		super.open();
		System.out.println(" Folding style phone ");
	}
	public void close() {
		super.close();
		System.out.println(" Folding style phone ");
	}
	public void call() {
		super.call();
		System.out.println(" Folding style phone ");
	}
}

(5) Client

public class Client {
	public static void main(String[] args) {
		//Get a folding phone (style + brand)
		Phone phone1 = new FoldedPhone(new XiaoMi());
		phone1.open();
		phone1.call();
		phone1.close();
		
		System.out.println("=======================");
		Phone phone2 = new FoldedPhone(new Vivo());
		phone2.open();
		phone2.call();
		phone2.close();
	}
}

10.4 source code analysis of bridge mode in JDBC

  1. For the Driver interface of Jdbc, if viewed from the bridge mode, the Driver is an interface. There can be MySQL Driver and Oracle Driver below, which can be used as implementation interface classes.
  2. Class diagram of bridging mode

10.5 precautions for bridging mode

  1. matters needing attention
    (1) It realizes the separation of abstract part and implementation part, which greatly provides the flexibility of the system and makes the abstract part and implementation part independent, which is helpful for the hierarchical design of the system, so as to produce a better structured system.
    (2) For the high-level part of the system, you only need to know the interface between the abstract part and the implementation part, and the other parts are completed by the specific business.
    (3) Bridging mode replaces multi-layer inheritance scheme, which can reduce the number of subclasses and reduce the management and maintenance cost of the system.
    (4) The introduction of bridging mode increases the difficulty of system understanding and design. Because the aggregation association relationship is based on the abstraction layer, developers are required to design and program for the abstraction
    (5) The bridging mode requires to correctly identify two independently changing dimensions (abstraction, and Implementation) in the system. Therefore, its scope of use has certain limitations, that is, it needs such an application scenario.
  2. Bridge mode and other application scenarios
    (1) JDBC Driver
    (2) Bank transfer system
    – transfer classification: online transfer, counter transfer, AMT transfer
    – transfer user type: ordinary user, silver card user, gold card user
    (3) Message management
    Message type: instant message, delayed message
    Message classification: SMS, email, QQ

Chapter 11 decorator design mode

11.1 Starbucks Coffee order project (CAFE)

  1. Coffee type / single product: Espresso, ShortBlack, longblack, decaf
  2. Seasoning: Milk, soy Milk, Chocolate
  3. It is required to have good expansibility, easy modification and maintenance when expanding new coffee varieties
  4. Use OO to calculate the cost of different kinds of coffee: customers can order single coffee or single coffee + seasoning combination.

11.2 traditional scheme

  1. Solution 1 - solve Starbucks Coffee order project

    (1) Drink is an abstract class that represents a drink
    (2) des is a description of coffee, such as the name of coffee
    (3) The cost() method is to calculate the cost and make an abstract method in the sink class
    (4) Decaf is a single coffee. It inherits Drink and realizes cost
    (5) Espresso & & milk is a single coffee + seasoning. There are many combinations
  2. Scheme 1 problem analysis
    In this design, there will be many categories. When we add a single coffee or a new seasoning, the number of categories will double and appear
    Explosion like
  3. Solution 2 - solve Starbucks Coffee orders (better)
    Scheme 1 because the combination of coffee single product + seasoning will double the number of classes, it can be improved to build seasoning into the Drink class, so as not to cause too many classes. So as to improve the maintainability of the project (as shown in the figure)

    (1) As shown in the figure "zone 1", the seasoning is built into the Drink. When the new class inherits the Drink class, it inherits the seasoning.
    (2) As shown in the figure "zone 2", the number of spices is returned through the has method (boolean or int type represents presence or absence or quantity).
  4. Scheme 2 problem analysis
    (1) Scheme 2 can control the number of classes without causing many classes
    (2) When adding or deleting a seasoning type, the maintenance of the code is very large
    (3) Considering that you can add multiple condiments, you can return hasMilk to a corresponding int

11.3 decorator mode

  1. definition
    (1) Decorator mode: dynamically attach new functions to objects. In terms of object function expansion, it is more flexible than inheritance, and the decorator pattern also embodies the open close principle (ocp)
    (2) The dynamic addition of new functions to objects and ocp principles mentioned here will be embodied in the form of code in later application examples,
  2. principle
    (1) Component body: for example, like the previous Drink
    (2) ConcreteComponent and Decorator ConcreteComponent: specific subjects, such as the previous single coffee
    (3) Decorator: decorator, such as spices
  3. Class diagram
    Between the Component and ConcreteComponent as shown in the figure, if there are many ConcreteComponent classes, you can also design a buffer layer to extract the common parts, and the abstraction layer is a class.

11.4 decorator mode to solve Starbucks Coffee orders

  1. Order in decorator mode: 2 points chocolate - 1 part milk LongBlank

    (1) Milk contains LongBlack
    (2) A Chocolate contains (milk + long black)
    (3) A Chocolate contains (Chocolate + milk + long black)
    (4) In this way, no matter what form of single coffee + seasoning combination, it can be easily combined and maintained through recursion.
  2. code implementation
    (1) Drink class
public abstract class Drink {
	public String des; // describe
	private float price = 0.0f;
	public String getDes() {
		return des;
	}
	public void setDes(String des) {
		this.des = des;
	}
	public float getPrice() {
		return price;
	}
	public void setPrice(float price) {
		this.price = price;
	}
	//Abstract method of calculating cost
	//Subclasses
	public abstract float cost();	
}

(2) coffee class

public class Coffee  extends Drink {
	@Override
	public float cost() {
		return super.getPrice();
	}	
}

(3) Single coffee: Espresso

public class Espresso extends Coffee {
	public Espresso() {
		setDes(" Italian Coffee ");
		setPrice(6.0f);
	}
}

LongBlack. ShortBlack similar
(4) Decorator class: decorator

public class Decorator extends Drink {
	private Drink obj;
	public Decorator(Drink obj) { //combination
		this.obj = obj;
	}
	@Override
	public float cost() {
		// getPrice own price
		return super.getPrice() + obj.cost();
	}
	@Override
	public String getDes() {
		// obj.getDes() outputs the information of the decorated person
		return des + " " + getPrice() + " && " + obj.getDes();
	}
}

(5) Seasoning class: Milk class
Specific Decorator, here is the seasoning

public class Milk extends Decorator {
	public Milk(Drink obj) {
		super(obj);
		setDes(" milk ");
		setPrice(2.0f); //Condiment price
	}
}

The Chocolate class is similar to the Soy class.
(6) Client

public class CoffeeBar {

	public static void main(String[] args) {
		// Order in decorator mode: 2 chocolate + 1 milk LongBlack

		// 1. Order a LongBlack
		Drink order = new LongBlack();
		System.out.println("Cost 1=" + order.cost());
		System.out.println("describe=" + order.getDes());

		// 2. order add a portion of milk
		order = new Milk(order);
		System.out.println("order Add a milk charge =" + order.cost());
		System.out.println("order Add a milk description = " + order.getDes());

		// 3. order add a chocolate
		order = new Chocolate(order);
		System.out.println("order Add a portion of milk and a portion of chocolate =" + order.cost());
		System.out.println("order Add a portion of milk and a portion of chocolate = " + order.getDes());

		// 3. order add a chocolate
		order = new Chocolate(order);
		System.out.println("order Add 1 milk and 2 chocolate =" + order.cost());
		System.out.println("order Add 1 part milk and 2 parts chocolate = " + order.getDes());
		
		Drink order2 = new DeCaf();
		System.out.println("order2 No coffee charge =" + order2.cost());
		System.out.println("order2 Coffee description = " + order2.getDes());
		
		order2 = new Milk(order2);
		System.out.println("order2 No charge for adding a portion of milk to coffee =" + order2.cost());
		System.out.println("order2 Add a milk description to the coffee = " + order2.getDes());
	}
}

11.5 source code analysis of decorator mode in JDK application

The IO structure of Java, FilterInputStream, is a decorator

Topics: Java Design Pattern