Design mode -- seven principles

Design pattern

Introduction to design mode

Definition

Design pattern is a set of object-oriented code design experience summary. It is a code design method that is repeatedly used in the field of programming, known by most people, and sorted.

Founder

Gang of four

essence

  1. It's an idea (the vast majority) and a technology
  2. It's a solution that developers can do quickly
  3. Point out the development direction and achieve the goal quickly

Purpose *

In the process of writing software, programmers are faced with many challenges, such as coupling, cohesion, maintainability, scalability, reusability, flexibility, etc. the design mode is to make the program (software) have the following advantages:

  1. Code reusability (i.e. code with the same function, not written multiple times)
  2. Readability (i.e. programming specification, easy for other programmers to read and understand)
  3. Extensibility (that is, when new functions need to be added, it is very convenient, which is called maintainability)
  4. Make the program present the characteristics of high cohesion and low coupling
  5. Engineering code writing
Golden sentence
  1. Design pattern contains the essence of object-oriented, "if you understand design pattern, you will understand the essence of object-oriented analysis and design (OOA/D)"
  2. Scott Mayers once said in his masterpiece Effective C + +: the difference between a C + + veteran and a C + + novice is that there are many scars on the back of their hands

Seven principles of design mode

The principle of design pattern is actually the principle that programmers should abide by when programming, and it is also the basis of various design patterns (i.e. why design patterns are designed in this way)

Single responsibility principle

Definition

For a class, there should be only one reason for its change

Examples of violation of the single principle

A class responsible for receiving information from users may be designed to also access the database and record the information sent by users.
Changes in the data persistence layer and the information structure sent by users will cause corresponding changes in this class (there are two reasons for this class to change, which violates the principle of single responsibility)

shortcoming

A class of occupation is huge, maintenance is more difficult, and reusability is reduced

Advantages of single responsibility

  1. Easy to maintain (avoid modifying one function to affect other functions)
  2. High reusability
  3. Reduce the complexity of a class. A class is only responsible for one principle, and the logic is simple
  4. Improve the readability and maintainability of the class
  5. Reduce risks caused by changes

Example

Check the operation of different vehicles

Scheme 1
/**
 * Vehicles
 */
class Vehicle {
	public void run(String vehicle) {
		System.out.println(vehicle + " Running on the road....");
	}
}

/**
 * Client
 */
public class SingleResponsibility1 {
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Vehicle vehicle = new Vehicle();
		vehicle.run("Motorcycle");
		vehicle.run("automobile");
		vehicle.run("aircraft");
	}

}
Analysis

In the run method of mode 1, the principle of single responsibility is violated. If the requirements are changed, such as adding more aircraft vehicles, and then adding more ships vehicles, the original design will have problems. With too many Vehicle responsibilities, changes are more likely to bring risks.

Improvement

It can be divided into different types according to different operation methods of vehicles

Option two
/**
 * Land transport
 */
class RoadVehicle {
	public void run(String vehicle) {
		System.out.println(vehicle + "Highway operation");
	}
}

/**
 * Air transport
 */
class AirVehicle {
	public void run(String vehicle) {
		System.out.println(vehicle + "Sky operation");
	}
}

/**
 * Underwater vehicles
 */
class WaterVehicle {
	public void run(String vehicle) {
		System.out.println(vehicle + "Running in water");
	}
}

/**
 * Client
 */
public class SingleResponsibility2 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		RoadVehicle roadVehicle = new RoadVehicle();
		roadVehicle.run("Motorcycle");
		roadVehicle.run("automobile");
		
		AirVehicle airVehicle = new AirVehicle();
		
		airVehicle.run("aircraft");
	}

}
Analysis

Although the principle of single responsibility is followed, there are many changes in this way, i.e. class decomposition and client modification. There are only three responsibilities here, which are not obvious. If there are many responsibilities, the disadvantages will be obvious.

Improvement

Modify the Vehicle class directly, and there will be less code to change

Option three
/**
 * Vehicles, split specific responsibilities into methods
 */
class Vehicle2 {
	
	public void run(String vehicle) {
		System.out.println(vehicle + " Running on the road....");
	}
	
	public void runAir(String vehicle) {
		System.out.println(vehicle + " Running in the sky....");
	}
	
	public void runWater(String vehicle) {
		System.out.println(vehicle + " Walking in water....");
	}

}

/**
 * Client
 */
public class SingleResponsibility3 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Vehicle2 vehicle2  = new Vehicle2();
		vehicle2.run("automobile");
		vehicle2.runWater("Ship");
		vehicle2.runAir("aircraft");
	}

}
Analysis
  1. This modification method does not make big changes to the original class, but only adds methods
  2. Although the principle of single responsibility is not observed at the class level, it is still observed at the method level. At that time, the modification operation was reduced, which was more in line with the actual development situation.

Extension of single responsibility principle (when can be violated)

Reason

Duty diffusion (duty a is divided into A1 and A2 for specific reasons)

Violation of the principle of single responsibility
  1. Simple enough logic is required to violate the single responsibility principle at the code level
  2. A sufficient number of methods in a class is required to violate the single responsibility principle at the method level

Interface isolation principle

Definition

Clients should not rely on interfaces they do not need, that is, a class's dependence on another class should be based on the smallest interface

Example

Scheme 1

//Interface
interface Interface1 {
	void operation1();
	void operation2();
	void operation3();
	void operation4();
	void operation5();
}

class B implements Interface1 {
	@Override
	public void operation1() {
		System.out.println("B Realized operation1");
	}
	@Override
	public void operation2() {
		System.out.println("B Realized operation2");
	}
	@Override
	public void operation3() {
		System.out.println("B Realized operation3");
	}
	@Override
	public void operation4() {
		System.out.println("B Realized operation4");
	}
	@Override
	public void operation5() {
		System.out.println("B Realized operation5");
	}
}

class D implements Interface1 {
	@Override
	public void operation1() {
		System.out.println("D Realized operation1");
	}
	@Override
	public void operation2() {
		System.out.println("D Realized operation2");
	}
	@Override
	public void operation3() {
		System.out.println("D Realized operation3");
	}
	@Override
	public void operation4() {
		System.out.println("D Realized operation4");
	}
	@Override
	public void operation5() {
		System.out.println("D Realized operation5");
	}
}

/**
 * A Class depends on (uses) class B through interface interface 1, but only uses 1,2,3 methods
 */
class A { 
	public void depend1(Interface1 i) {
		i.operation1();
	}
	public void depend2(Interface1 i) {
		i.operation2();
	}
	public void depend3(Interface1 i) {
		i.operation3();
	}
}

/**
 * C Class depends on (uses) class D through interface interface 1, but only uses methods 1,4,5
 */
class C { 
	public void depend1(Interface1 i) {
		i.operation1();
	}
	public void depend4(Interface1 i) {
		i.operation4();
	}
	public void depend5(Interface1 i) {
		i.operation5();
	}
}
Analysis

Interface isolation principle is not used. Class a depends on class B through interface Interface1, and class C depends on class D through interface Interface1. If interface Interface1 is not the minimum interface for class A and class C, then class B and class D must implement methods they do not need.

Option two
Improvement

According to the principle of isolation, it should be handled as follows:
The interface interface 1 is divided into several independent interfaces. Class A and class C respectively establish the dependency relationship with the interfaces they need. That is to say, the principle of interface isolation is adopted

// Interface 1
interface Interface1 {
	void operation1();

}

// Interface 2
interface Interface2 {
	void operation2();

	void operation3();
}

// Interface 3
interface Interface3 {
	void operation4();

	void operation5();
}

class B implements Interface1, Interface2 {
	@Override
	public void operation1() {
		System.out.println("B Realized operation1");
	}

	@Override
	public void operation2() {
		System.out.println("B Realized operation2");
	}

	@Override
	public void operation3() {
		System.out.println("B Realized operation3");
	}

}

class D implements Interface1, Interface3 {
	@Override
	public void operation1() {
		System.out.println("D Realized operation1");
	}

	@Override
	public void operation4() {
		System.out.println("D Realized operation4");
	}

	@Override
	public void operation5() {
		System.out.println("D Realized operation5");
	}
}

/**
 * A Class through Interface1,Interface2 depends on (uses) class B, but only uses methods 1, 2, 3
 */
class A { 
	public void depend1(Interface1 i) {
		i.operation1();
	}

	public void depend2(Interface2 i) {
		i.operation2();
	}

	public void depend3(Interface2 i) {
		i.operation3();
	}
}

/**
 * C Class through Interface1,Interface3 depends on (uses) class D, but only uses methods 1,4,5
 */
class C { 
	public void depend1(Interface1 i) {
		i.operation1();
	}

	public void depend4(Interface3 i) {
		i.operation4();
	}

	public void depend5(Interface3 i) {
		i.operation5();
	}
}

/**
 * Client
 */
public class Segregation1 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		// Use a handful of
		A a = new A();
		a.depend1(new B()); // Class A depends on class B through interface
		a.depend2(new B());
		a.depend3(new B());

		C c = new C();

		c.depend1(new D()); // Class C depends on class D through interface
		c.depend4(new D());
		c.depend5(new D());

	}

}

Using the principle of interface isolation, the dependence of one class on another should be based on the minimum interface to avoid the implementation of unnecessary methods.

Dependence Inversion Principle

Definition

  1. High level modules should not rely on low level modules, both of which should rely on their abstractions
  2. Abstraction should not depend on details. Details should depend on abstraction
  3. The central idea of dependency inversion is interface oriented programming

Design concept:

  1. Abstract things are much more stable than details. The architecture based on abstraction is more stable than that based on details. In java, abstraction refers to an interface or an abstract class, and details are concrete implementation classes
  2. The purpose of using an interface or abstract class is to formulate a specification without any specific operation. The task of presenting details is left to their implementation class to complete

essence

Through abstraction (Interface) or abstract class, the implementation of each class or module is independent of each other and does not affect each other, so as to realize the loose coupling dependency inversion principle between modules

Standard

  1. Every class should inherit from the interface or abstract class, or both.
    This is the basic requirement of dependency inversion. Interfaces and abstract classes are abstract. Only with abstraction can dependency inversion be possible
  2. The display type of variable should be interface or abstract class as far as possible
    Many books say that the type of variable must be interface or abstract class, which is a bit absolute. For example, a tool class generally does not need interface or abstract class. In addition, if you want to use the clone method of the class, you must use the implementation class. This is a specification provided by JDK
  3. Classes try to avoid deriving from concrete classes.
    If a project is in development state, it should not be specific. Classes derive subclasses, but this is not absolute, because people make mistakes, sometimes design defects are inevitable, so as long as no more than two levels of inheritance are acceptable. Especially for project maintenance engineers, this rule can be ignored basically, because maintenance work is basically to do extension development and repair. Through an inheritance relationship and a method coverage, a large Bug can be corrected. Why inherit the highest base class?
  4. Try not to override the methods of the base class.
    If the base class is an abstract class and the method has been implemented, the subclass should not be covered. The dependence between classes is abstraction, covering abstract methods, which will have a certain impact on the stability of dependence

Significance

The traditional design method of process system tends to make the high-level module depend on the low-level module, and the abstract level depends on the concrete level. The meaning of reverse is to reverse the wrong dependency, requiring clients to rely on abstract coupling

Abstraction layer

The abstract layer should include the business logic of the application system and the macro strategic decision which is important to the whole system.

Concrete layer

The specific level contains some secondary, implementation related algorithms, logic and tactical decisions. It's all quite accidental

Extension - violation of dependency reversal principle

If the function of a class is likely to remain unchanged in the future, the dependency inversion principle is not needed

Example

Programming to complete the function of Person receiving messages

Scheme 1
class Email {
	public String getInfo() {
		return "Email information: hello,world";
	}
}

//Complete the function of Person receiving messages
class Person {
	public void receive(Email email ) {
		System.out.println(email.getInfo());
	}
}

public class DependecyInversion {

	public static void main(String[] args) {
		Person person = new Person();
		person.receive(new Email());
	}

}
Analysis
  1. Simple, easy to think of
  2. If the objects we get are wechat, SMS, etc., we need to add new classes, and Perons also need to add corresponding receiving methods
Improvement

Solution: introduce an abstract interface IReceiver to represent the receiver, so that the Person class depends on the interface IReceiver
Because Email, WeiXin, etc. belong to the receiving scope, it's ok for them to implement IReceiver interface respectively, so we rely on the reverse principle for symbols

Option two
//Defining interfaces
interface IReceiver {
	public String getInfo();
}

class Email implements IReceiver {
	@Override
	public String getInfo() {
		return "Email information: hello,world";
	}
}

//Add WeChat
class WeiXin implements IReceiver {
	@Override
	public String getInfo() {
		return "WeChat info: hello,ok";
	}
}

//Mode 2
class Person {
	//Here we rely on the interface
	public void receive(IReceiver receiver ) {
		System.out.println(receiver.getInfo());
	}
}

public class DependecyInversion {

	public static void main(String[] args) {
		//Client does not need to change
		Person person = new Person();
		person.receive(new Email());
		
		person.receive(new WeiXin());
	}

}

Three ways of dependency transfer and application cases

Interface transfer
Example
 //ITV interface
interface ITV {
    public void play ();
}

// Switch interface
interface IOpenAndClose {
    public void open ( ITV tv ); //Abstract method, receive interface
}

class ChangHong implements ITV {
    @Override
    public void play () {
        // TODO Auto-generated method stub
        System.out.println("Changhong TV, turn it on");
    }
}

// Implementation interface
class OpenAndClose implements IOpenAndClose {
    public void open ( ITV tv ) {
        tv.play();
    }
}

public class DependencyPass {
    public static void main ( String[] args ) {
        ChangHong changHong = new ChangHong();
		OpenAndClose openAndClose = new OpenAndClose();
		//Passing dependencies through interfaces
		openAndClose.open(changHong);   
    }
}
Construction method transfer
Example
interface IOpenAndClose {
	//Abstract method
	public void open (); 
}

//ITV interface
interface ITV { 
	public void play ();
}

class OpenAndClose implements IOpenAndClose {
	public ITV tv; //member

	public OpenAndClose ( ITV tv ) { //constructor
		this.tv = tv;
	}

	public void open () {
		this.tv.play();
	}
}

public class DependencyPass {

    public static void main ( String[] args ) {
        ChangHong changHong = new ChangHong();
		        //Dependency passing through the constructor
		OpenAndClose openAndClose = new    OpenAndClose(changHong);
		openAndClose.open();
 
    }

}
setter delivery
Example
interface IOpenAndClose {
    public void open (); // Abstract method

    public void setTv ( ITV tv );
}

interface ITV { 
    public void play ();
}

class OpenAndClose implements IOpenAndClose {
    private ITV tv;

    @Override
    public void setTv ( ITV tv ) {
        this.tv = tv;
    }

    @Override
    public void open () {
        this.tv.play();
    }
}

class ChangHong implements ITV {
    @Override
    public void play () {
        System.out.println("Changhong TV, turn it on");
    }
}

 public static void main ( String[] args ) {
        ChangHong changHong = new ChangHong();
        //Dependency passing through setter method
        OpenAndClose openAndClose = new OpenAndClose();
        openAndClose.setTv(changHong);
        openAndClose.open();
 }

Notes and details on the principle of relying on reversal

  1. Low level modules should have abstract classes or interfaces as much as possible, or both, with better program stability
  2. The declaration type of variable should be abstract class or interface as far as possible, so that there is a buffer between our variable reference and the actual object, which is conducive to program expansion and optimization
  3. Follow the principle of Richter's substitution when inheriting

Richter's principle of substitution

Definition

Subtypes must be able to replace their parent types without affecting the function (behavior) of the program, which is the specification of the concrete steps to achieve abstraction.

brief introduction

  1. If there is object o2 of type T2 for each object o1 of type T1, so that when all objects o1 defined by T1 are replaced by o2, the behavior of program P does not change, then type T2 is a subtype of type T1. In other words, all references to base classes must be able to transparently use objects of their subclasses.
  2. When using inheritance, follow the principle of leech substitution, and try not to override the methods of the parent class in the child class
  3. The Riemannian substitution principle tells us that inheritance actually enhances the coupling of the two classes, and that, when appropriate, problems can be solved through aggregation, composition, and dependency.

inherit

  1. Diversify the parent class, produce multi seed class, and keep the common characteristics of the parent class
  2. Subclasses have their own characteristics and inherit the common characteristics of their parents
  3. Inheritor (subclass) can be understood as the specialization of inheritee (parent)
Definition in object-oriented
  1. Inheritance is a technique to create a new class based on the definition of an existing class. An existing class is a parent class, and a new class created is a child class of an existing class. A child class can add new methods or use the methods of the parent class, but it cannot selectively inherit the parent class.
  2. Inheritance represents an intersecting relationship. One class object can inherit the data members and member methods of another class

Example

Problems caused by inheritance
class A {
	// Returns the difference between two numbers
	public int func1(int num1, int num2) {
		return num1 - num2;
	}
}

/**
 * B Class inherits A
 * A new function has been added: add two numbers and sum with 9
 */
class B extends A {
	//Here, the class A method is rewritten, which may be unconscious
	public int func1(int a, int b) {
		return a + b;
	}

	public int func2(int a, int b) {
		return func1(a, b) + 9;
	}
}

public class Liskov {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		A a = new A();
		System.out.println("11-3=" + a.func1(11, 3));
		System.out.println("1-8=" + a.func1(1, 8));

		System.out.println("-----------------------");
		B b = new B();
		System.out.println("11-3=" + b.func1(11, 3));//The original idea here is to find 11-3, but what can be found is 11 + 3
		System.out.println("1-8=" + b.func1(1, 8));// The original idea here is to find 1-8, but what can be found is 1 + 8
		System.out.println("11+3+9=" + b.func2(11, 3));
	}

}
Analysis

There was an error in the normal subtraction function. The reason is that class B inadvertently rewrites the method of the parent class, resulting in the original function error. In actual programming, we often complete new functions by rewriting the method of the parent class, which is simple to write, but the reusability of the whole inheritance system is poor. Especially when the running polymorphism is frequent

Improvement

The general approach is: the original parent class and child class inherit a more popular base class. The original inheritance relationship is removed and replaced by dependency, aggregation, combination and other relationships

/**
 * Create a more basic base class
 */
class Base {
	//Write more basic methods and members to Base class
}

class A extends Base {
	// Returns the difference between two numbers
	public int func1(int num1, int num2) {
		return num1 - num2;
	}
}

/**
 * B Class inherits A
 * A new function has been added: add two numbers and sum with 9
 */
class B extends Base {
	//If B needs to use method A, use combination relationship
	private A a = new A();
	
	//Here, the class A method is rewritten, which may be unconscious
	public int func1(int a, int b) {
		return a + b;
	}

	public int func2(int a, int b) {
		return func1(a, b) + 9;
	}
	
	//We still want to use method A
	public int func3(int a, int b) {
		return this.a.func1(a, b);
	}
}

public class Liskov {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		A a = new A();
		System.out.println("11-3=" + a.func1(11, 3));
		System.out.println("1-8=" + a.func1(1, 8));

		System.out.println("-----------------------");
		B b = new B();
		//Because class B no longer inherits class A, the caller will no longer use func1 to subtract
		//The function completed by the call will be clear
		System.out.println("11+3=" + b.func1(11, 3));//The original idea here is to find 11 + 3
		System.out.println("1+8=" + b.func1(1, 8));// 1+8
		System.out.println("11+3+9=" + b.func2(11, 3));
		
		//Class A related methods can still be used with combination
		System.out.println("11-3=" + b.func3(11, 3));// The original idea here is to find 11-3
	}

}

Opening and closing principle

It is the core of all principles in object-oriented development. The key is to realize abstraction and get concrete examples from abstraction

Definition

The entity objects (modules, classes, functions, etc.) of a program should be extensible but not modifiable

Understand
  1. The extension of the existing program takes the priority of adding new classes or modules, rather than modifying the existing classes or modules
  2. A software entity such as class, module and function should be open to extension (to provider) and closed to modification (to user). Build framework with abstraction and extend details with implementation.
  3. When the software needs to change, try to achieve the change by expanding the behavior of the software entity, rather than by modifying the existing code.

Reason

The goal of software design itself is to encapsulate changes and reduce coupling. The open and closed principle is the direct realization of this goal (Richter's principle is also to achieve this goal - through the realization of the best and correct inheritance level)

Features

Open to expansion
Closed to modification -- Open Closed Principle (core)

Significance

  1. The core idea is to face Abstract Programming, not concrete programming, because abstract is relatively stable.
  2. Let the class depend on the fixed abstraction, so it is closed for modification; through the object-oriented inheritance and polymorphism mechanism, it can realize the inheritance of the abstraction, and change the inherent behavior by rewriting its method, so as to realize the new extension method, so it is open for extension
  3. The open closed principle can provide new behaviors by expanding the existing software system, so as to meet the new needs of software, and make the changing software have certain adaptability and flexibility
  4. The existing software modules, especially the most important abstract modules, can not be modified, which makes the changing software system have certain stability and continuity

Example

Realize a drawing function

Scheme 1

//Shape class, base class
class Shape {
	int m_type;
}

//rectangle
class Rectangle extends Shape {
	Rectangle() {
		super.m_type = 1;
	}
}

//circular
class Circle extends Shape {
	Circle() {
		super.m_type = 2;
	}
}

//New triangle drawing
class Triangle extends Shape {
	Triangle() {
		super.m_type = 3;
	}
}

/**
 * This is a class [user] for drawing
 */
class GraphicEditor {
	//Receive the Shape object and draw different shapes according to the type
	public void drawShape(Shape s) {
		if (s.m_type == 1) {
			drawRectangle(s);
		} else if (s.m_type == 2) {
			drawCircle(s);
		} else if (s.m_type == 3) {
			drawTriangle(s);
		}
	}

	//draw rectangle
	public void drawRectangle(Shape r) {
		System.out.println(" draw rectangle ");
	}

	//Draw circles
	public void drawCircle(Shape r) {
		System.out.println(" Draw circles ");
	}
	
	//Draw triangle
	public void drawTriangle(Shape r) {
		System.out.println(" Draw triangle ");
	}
}

public class Ocp {
	public static void main(String[] args) {
		//Use to see the problems
		GraphicEditor graphicEditor = new GraphicEditor();
		graphicEditor.drawShape(new Rectangle());
		graphicEditor.drawShape(new Circle());
		graphicEditor.drawShape(new Triangle());
	}
}
Analysis
  1. The advantages are easy to understand and operate.
  2. The disadvantage is that it violates the ocp principle of design pattern, that is, it is open to extension (provider) and closed to modification (user). That is, when we add new functions to the class, we try not to modify the code, or modify the code as little as possible
  3. For example, we need to add a new graphic type triangle at this time, with many modifications
Improvement

Idea: make the creation of Shape class into abstract class, and provide an abstract draw method, so that the subclass can realize it. So when we have a new type of graphics, we only need to let the new graphics class inherit Shape and implement draw method, and the user's code does not need to be modified - > full of opening and closing principles

//This is a class [user] for drawing
class GraphicEditor {
	//Receive Shape object and call draw method
	public void drawShape(Shape s) {
		s.draw();
	}
}

//Shape class, base class
abstract class Shape {
	int m_type;
	public abstract void draw();//Abstract method
}

class Rectangle extends Shape {
	Rectangle() {
		super.m_type = 1;
	}

	@Override
	public void draw() {
		System.out.println(" draw rectangle ");
	}
}

class Circle extends Shape {
	Circle() {
		super.m_type = 2;
	}
	@Override
	public void draw() {
		System.out.println(" Draw circles ");
	}
}

//New triangle drawing
class Triangle extends Shape {
	Triangle() {
		super.m_type = 3;
	}
	@Override
	public void draw() {
		System.out.println(" Draw triangle ");
	}
}

//Add a new graphic
class OtherGraphic extends Shape {
	OtherGraphic() {
		super.m_type = 4;
	}
	@Override
	public void draw() {
		System.out.println(" Draw other graphics ");
	}
}

public class Ocp {

	public static void main(String[] args) {
		GraphicEditor graphicEditor = new GraphicEditor();
		graphicEditor.drawShape(new Rectangle());
		graphicEditor.drawShape(new Circle());
		graphicEditor.drawShape(new Triangle());
		graphicEditor.drawShape(new OtherGraphic());
	}

}

Dimiter's law

Definition

If there is no direct communication between two classes, the two classes should not interact directly. If one class needs to call a method of the other class, the call can be forwarded through a third party

Fundamental thought

Classes need to be loosely coupled as much as possible. The weaker the coupling, the more conducive to reuse. Information hiding will promote software reuse

premise

  1. Each class should minimize the access rights of its members. The class should wrap its own private state. Fields or behaviors that do not need to be known by other classes should not be disclosed
  2. Minimize permissions on properties or methods

Basic introduction

  1. One object should have the least knowledge of other objects
  2. The closer the relationship between class and class, the greater the coupling degree
    Demeter principle is also called the least known principle, that is, the less a class knows about the class it depends on, the better. In other words, no matter how complex the dependent class is, try to encapsulate the logic inside the class. In addition to the public method provided, no information is disclosed to the public
  3. Dmitry 's law also has a simpler definition: communicate only with direct friends
  4. Direct friend: every object has a coupling relationship with other objects. As long as there is a coupling relationship between two objects, we say that the two objects are friends. There are many ways of coupling, such as dependency, association, combination, aggregation, etc. Among them, * * we call the class in the occurrence member variable, method parameter and method return value as the direct friend, while the class in the local variable is not the direct friend. **That is to say, it is better not to appear in the form of local variables in a strange class.

Example

There is a school with subordinate schools and headquarters. Now it is required to print out the id of the staff in the school headquarters and the id of the staff in the school

Scheme 1
/School headquarters staff
class Employee {
	private String id;

	public void setId(String id) {
		this.id = id;
	}

	public String getId() {
		return id;
	}
}


//Staff of the College
class CollegeEmployee {
	private String id;

	public void setId(String id) {
		this.id = id;
	}

	public String getId() {
		return id;
	}
}


//Management of staff in Management College
class CollegeManager {
	//All employees returning to the College
	public List<CollegeEmployee> getAllEmployee() {
		List<CollegeEmployee> list = new ArrayList<CollegeEmployee>();
		for (int i = 0; i < 10; i++) { //Here we add 10 employees to the list
			CollegeEmployee emp = new CollegeEmployee();
			emp.setId("College staff id= " + i);
			list.add(emp);
		}
		return list;
	}
}

//School management

//Analyze which Employee and CollegeManager are the direct friend classes of the SchoolManager class
//The CollegeEmployee is not a direct friend, but a strange class, which violates Dimitar's law 
class SchoolManager {
	//Staff returning to school headquarters
	public List<Employee> getAllEmployee() {
		List<Employee> list = new ArrayList<Employee>();
		
		for (int i = 0; i < 5; i++) { //Here we add 5 employees to the list
			Employee emp = new Employee();
			emp.setId("Staff of school headquarters id= " + i);
			list.add(emp);
		}
		return list;
	}

	//This method can output the information (id) of school headquarters and college employees
	void printAllEmployee(CollegeManager sub) {
		
		//Analysis problem
		//1. The CollegeEmployee here is not a direct friend of SchoolManager
		//2. The collegeemployee appears in the SchoolManager as a local variable
		//3. Violation of Dimitar's law 
		
		//Access to college staff
		List<CollegeEmployee> list1 = sub.getAllEmployee();
		System.out.println("------------College staff------------");
		for (CollegeEmployee e : list1) {
			System.out.println(e.getId());
		}
		//Access to school headquarters staff
		List<Employee> list2 = this.getAllEmployee();
		System.out.println("------------Staff of school headquarters------------");
		for (Employee e : list2) {
			System.out.println(e.getId());
		}
	}
}

//Client
public class Demeter1 {

	public static void main(String[] args) {
		//Created a SchoolManager object
		SchoolManager schoolManager = new SchoolManager();
		//Output the employee id of the college and the employee information of the school headquarters
		schoolManager.printAllEmployee(new CollegeManager());

	}

}
Analysis

The CollegeEmployee class should not appear in the school management class of SchoolManager, which violates Dimitar's law. The information of CollegeEmployee employees is handed over to the management of CollegeManager college. According to Dimiter's law, such coupling of indirect friend relationships in a class should be avoided.

Improvement
//School headquarters staff
class Employee {
	private String id;

	public void setId(String id) {
		this.id = id;
	}

	public String getId() {
		return id;
	}
}


//Staff of the College
class CollegeEmployee {
	private String id;

	public void setId(String id) {
		this.id = id;
	}

	public String getId() {
		return id;
	}
}


//Management of the staff of the school of management
class CollegeManager {
	//All employees returning to the College
	public List<CollegeEmployee> getAllEmployee() {
		List<CollegeEmployee> list = new ArrayList<CollegeEmployee>();
		for (int i = 0; i < 10; i++) { //Here we add 10 employees to the list
			CollegeEmployee emp = new CollegeEmployee();
			emp.setId("College staff id= " + i);
			list.add(emp);
		}
		return list;
	}
	
	//Output information of college staff
	public void printEmployee() {
		//Access to college staff
		List<CollegeEmployee> list1 = getAllEmployee();
		System.out.println("------------College staff------------");
		for (CollegeEmployee e : list1) {
			System.out.println(e.getId());
		}
	}
}

//School management

//Analyze which Employee and CollegeManager are the direct friend classes of the SchoolManager class
class SchoolManager {
	//Staff returning to school headquarters
	public List<Employee> getAllEmployee() {
		List<Employee> list = new ArrayList<Employee>();
		
		for (int i = 0; i < 5; i++) { //Here we add 5 employees to the list
			Employee emp = new Employee();
			emp.setId("Staff of school headquarters id= " + i);
			list.add(emp);
		}
		return list;
	}

	//This method can output the information (id) of school headquarters and college employees
	void printAllEmployee(CollegeManager sub) {
		
		//Analysis problem
		//1. Encapsulate the employee method of the output college into the CollegeManager
		sub.printEmployee();
	
		//Access to school headquarters staff
		List<Employee> list2 = this.getAllEmployee();
		System.out.println("------------Staff of school headquarters------------");
		for (Employee e : list2) {
			System.out.println(e.getId());
		}
	}
}

public static void main(String[] args) {
		System.out.println("~~~The improvement of using Dimiter's law~~~");
		//Created a SchoolManager object
		SchoolManager schoolManager = new SchoolManager();
		//Output the employee id of the college and the employee information of the school headquarters
		schoolManager.printAllEmployee(new CollegeManager());

}

Notes and details of Dimiter's law

  1. The core of Dimiter's law is to reduce the coupling between classes
  2. But note: because each class reduces unnecessary dependency, Dimitar's law only requires reducing coupling between classes (objects), not no dependency at all

Principle of composite reuse

Definition

Try not to use class inheritance, but try to use composition / aggregation. Prioritizing the composition / aggregation of objects helps keep every class on the system encapsulated and focused on a single task

Understand

polymerization
  1. Essence is the aggregation of quotation
  2. It is a relatively "strong" ownership relationship, reflecting the strict relationship between the whole and the part, and the whole and the part have the same life cycle
Synthesis
  1. Essence is the aggregation of values
  2. It is a relatively "strong" ownership relationship, reflecting the strict relationship between the whole and the part, and the whole and the part have the same life cycle
"Is-A"
  1. Representing a class is a kind of another class
  2. There is a clear "Is-A" relationship, and inheritance is considered when the relationship is stable and unchanging
  3. Two classes must conform to the Richter substitution principle to be "Is-A" (the subtype must be able to replace the parent type without affecting the operation of the program)
"Has-A"

Representing a class is a role of another class

Example
  1. The relationship between fish and fish is aggregation, and the relationship between arm and human is synthesis
  2. Human being is a kind of human being, and "manager", "university student" and "employee" are sub types of human being. If we consider it from the perspective of inheritance, it is unreasonable (if a person is a college student, then he cannot be a manager). So we should use synthesis

Advantage

  1. The inheritance level between classes can keep a small scale, and it is unlikely to grow into an uncontrollable large unit
  2. The only way a new object can access a subobject is through its interface
  3. This reuse is black box reuse, because the internal details of sub objects are invisible to the new object
  4. Better support for encapsulation features
  5. The interdependency in implementation is relatively small
  6. Each new class can focus on one task
  7. It can be done dynamically at runtime, and new objects can dynamically reference objects of the same type as sub objects
  8. Can be applied to any environment

shortcoming

There will be more objects to manage

Conditions of use

  1. When a subclass is a special kind ("Has-A") of the parent, rather than a role ("Is-A") of the parent
  2. There will never be a situation where you need to replace a subclass with a subclass of another class
  3. A subclass has the responsibility to extend the parent, not override the methods of the parent
  4. Inheritance can only be used if it makes sense from a taxonomic point of view
Example 1

Example two
  1. The relationship between generals and soldiers is aggregation (Generals can have soldiers, but soldiers are not part of generals, because generals can not have all soldiers); the relationship between emperors and generals is synthesis (emperors own the world, generals are part of the emperor)
  2. Interface the minister and the soldier
  3. The general implements the minister's interface and has a collection of soldier interfaces (aggregation)
  4. Specific soldier implements soldier interface
  5. The emperor has an interface set of Ministers

Core idea of design principle

  1. Find out the possible changes in the application, separate them, and don't mix them with the code that doesn't need to change.
  2. Program for the interface, not for the implementation.
  3. Strive for loose coupling design between interactive objects
19 original articles published, 10 praised, 3471 visited
Private letter follow

Tags: Programming less Database Java

Posted on Tue, 10 Mar 2020 01:21:14 -0400 by joeynovak