Six principles of Java design pattern

Posted by chen2424 on Wed, 09 Feb 2022 08:43:10 +0100

catalogue

Single responsibility principle

Open and closed principle

Internal Substitution Principle

Dependency Inversion Principle

Dimitri principle

Interface isolation principle

The six principles of design pattern are the principle of single responsibility, the principle of open and closed, the principle of internal substitution, the principle of dependence, the principle of dimitt and the principle of interface isolation.

Single responsibility principle

As far as a class is concerned, there should be only one reason for its change

Generally speaking, we should not let a class assume too many responsibilities. If a class assumes too many responsibilities, it is equivalent to coupling these responsibilities. The change of a responsibility may weaken or inhibit the ability of the class to complete other responsibilities. This coupling will lead to fragile design, and the design will be damaged when changes occur. For example, we can see that some Android developers write Bean files and network data processing in the Activity. If there is a list, the Adapter is also written in the Activity. As for the reason for doing so, there is no reason to be easy to find except for being simple and rough. Wouldn't it be better to split it into other categories? If the Activity is too bloated and has too many lines, it is obviously not a good thing. If we want to modify the Bean file, network processing and Adapter need to use this Activity to modify, which will lead to too many reasons for the change of this Activity, and we also have a headache in version maintenance. This is a serious violation of the definition: for a class, there should be only one reason for its change. The division of single responsibility is not very clear, and often depends on personal experience. Therefore, it is a controversial but extremely important principle

Open and closed principle

Classes, modules, functions, etc. should be extensible, but not modifiable

Open and closed have two meanings: one is open to extension and the other is closed to modification. For developer leso, the requirements must change, but if there are new requirements, we have to change the class again, which is obviously a headache. Therefore, when we design the program, we should ensure the relative stability in the face of the change of requirements as much as possible, and try to realize the change through expansion instead of modifying the original code. Suppose we want to implement a list. At first, we only have the function of query. Later, the product will add and delete functions in a few days. Most people's practice is to write a method, and then pass in different value control methods to achieve different functions. But if we want to add new functions, we have to modify the method. Using the development closure principle to solve this problem is to add an abstract function class and let add, delete and query as subclasses of this abstract function class. In this way, if we add new functions, you will find that you do not need to modify the original class. You only need to add a subclass of the function class to implement the method of the function class

Sample Demo

//Defines an abstract animal class with a method
public abstract class AniMal {
    abstract void ObjectX();
}


//Subclass cat implementation abstract method
class Cat extends AniMal {

    @Override
    void ObjectX() {
        System.out.println();
    }
}

//Subclass dog implementation abstract method
class Dog extends AniMal {

    @Override
    void ObjectX() {
        System.out.println();
    }
}

Internal Substitution Principle

All places that reference the base class (parent class) must be able to use the objects of its subclasses transparently

If a base class object is replaced by its subclass object in the software, the program will not produce any errors and exceptions, and the reverse is not true. If a software entity uses a subclass object, it may not be able to use a subclass object. The principle of Chinese style substitution is one of the important ways to realize the principle of opening and closing. Since subclass objects can be used wherever base class objects are used, try to use the base class type to define the object in the program, and determine its subclass type at runtime and replace the parent class object with subclass objects. The following issues need to be paid attention to when using the Li formula replacement principle: All methods of the subclass must be declared in the parent class, or the subclass must implement all methods declared in the parent class. According to the principle of Li replacement, in order to ensure the scalability of the system, the parent class is usually used to define in the program. If a method exists only in a subclass and does not provide a corresponding declaration in the parent class, it cannot be used in an object defined by the parent class. When we apply the principle of internal substitution, we try to design the parent class as an abstract class or interface, let the child class inherit the parent class or implement the parent interface, and implement the methods declared in the parent class. When running, the child class instance replaces the parent class instance. We can easily expand the system function and modify the code of the original child class disorderly at the same time; Adding new functions can be achieved by adding a new subclass. The principle of Chinese style substitution is one of the concrete means to realize the principle of opening and closing. In the Java language, in the compilation stage, the java compiler will check whether a program complies with the Li formula replacement principle. This is an implementation independent, purely syntactic check, but the check of the java compiler has limitations

Demo

// Color color
// Flavor smell
// Apples
class Apple{
    void Color(){
        System.out.println("gules");
    }
    void Flavour(){
        System.out.println("fragrant");
    }
}
 
//Pack - fruit packaging
//describe - fruit description
//getMessage - return specific information
class Pack{
    private Apple apple;
 
     void setApple(Apple apple) {
        this.apple = apple;
    }
 
     void getMessage(){
        apple.Color();
        apple.Flavour();
    }
}
 
//buyer - / specific scenario
 class Buyer{
    public static void main(String[] args) {
        Pack pack=new Pack();
        pack.setApple(new Apple());
        pack.getMessage();
    }
}
  

If we want to add a fruit class at this time, do we need to change the Pack wrapper class, add another class object, and then import it when it is called.

If I have five or six categories, wouldn't my packaging category be very troublesome?

Now let's modify the Demo

abstract class Frits {
    abstract void Color();
    abstract void Flavour();
}
 
// Color color
// Flavor smell
// Apples
class Apple extends Frits {
    public void Color() {
        System.out.println("gules");
    }
 
    public void Flavour() {
        System.out.println("sweet");
    }
}
 
//Banana
//Stroe specific storage methods
class Banana extends Frits {
    //Unique method
    public void Store(){
        System.out.println("Storage instructions");
    }
 
    @Override
    public void Color() {
        System.out.println("yellow");
    }
 
    @Override
    public void Flavour() {
        System.out.println("fragrant and sweet");
        Store();
    }
}
 
//Pack - fruit packaging
//describe - fruit description
//getMessage - return specific information
class Pack {
    private Frits frits;
 
    public void setFrits(Frits frits) {
        this.frits = frits;
    }
 
    void getMessage() {
        frits.Color();
        frits.Flavour();
    }
}
 
 
//buyer - / specific scenario
class Buyer {
    public static void main(String[] args) {
        Pack pack = new Pack();
 
        pack.setFrits(new Apple());
        pack.getMessage();
        pack.setFrits(new Banana());
        pack.getMessage();
    }
}

In general terms, subclasses can extend the functions of the parent class, but cannot change the original functions of the parent class:

1. Subclasses can realize the abstraction of the parent class, but they cannot override the non abstract methods of the parent class

2. You can add your own unique methods in the subclass.

3. When the method of the subclass overloads the method of the parent class, the preconditions of the method are looser than the input of the method of the parent class.

4. When the method of the subclass implements the abstract method of the parent class, the post condition of the method is stricter than that of the parent class.

Dependency Inversion Principle

The high-level module (caller) should not rely on the low-level module, and both should rely on abstraction. Abstractions should not depend on details (implementation classes), and details should depend on abstractions.

In java, abstraction refers to an interface or an abstract class, both of which cannot be instantiated directly; Details are implementation classes. Details are generated by implementing interfaces or inheriting abstract classes, that is, objects generated by adding a keyword new. The high-level module is the calling end, and the low-level module is the specific implementation class. The expression of dependency inversion principle in java is that the dependency between modules occurs through abstraction, and there is no direct dependency between implementation classes. Its dependency is generated through interfaces or abstract classes. If classes and classes depend directly on details, they will be directly coupled. In this way, the dependent code will be modified at the same time, which limits the scalability.

class Cat {
    void Cry() {
        System.out.println("Meow meow");
    }
}

class Dog {
    void Cry() {
        System.out.println("Wangwang");
    }
}


class Animal {
    void Cry(Cat cat, Dog dog) {
        cat.Cry();
        dog.Cry();
    }
}

class Test{
    public static void main(String[] args) {
        new Animal().Cry(new Cat(),new Dog());
    }
}

The above code seems to have no problem, but if we have other Animal subclasses, we have to change the Animal class and pass its object as a parameter into the Cry method. In the dependency inversion principle, the dependency between modules occurs through abstraction, and there is no direct dependency between implementation classes. The above Demo has violated this principle. Let's modify it.

interface Dongwu{
    void Cry();
}

class Cat implements Dongwu{
    public void Cry() {
        System.out.println("Meow meow");
    }
}

class Dog implements Dongwu{
    public void Cry() {
        System.out.println("Wangwang");
    }
}


class Animal {
    Dongwu dongwu;

    public void setDongwu(Dongwu dongwu) {
        this.dongwu = dongwu;
    }

    void Cry() {
       dongwu.Cry();
    }
}

class Test{
    public static void main(String[] args) {
        Animal animal=new Animal();
        
        animal.setDongwu(new Dog());
        animal.Cry();
        animal.setDongwu(new Cat());
        animal.Cry();
    }
}

We added an interface with an Animal call method, and then let the Animal classes implement this interface, and then pass the dependent object through the set method. In this way, if we add other Animal classes, we only need to implement the corresponding interface without changing our management class Animal.

Dimitri principle

A software entity should interact less with other entities.

This is also known as the best knowledge principle. If a system conforms to the Demeter principle, when one module is modified, it will affect other modules as little as possible. The Demeter principle requires that we should minimize the interaction between objects when designing a system. If two objects do not have to lead directly to each other, then the two objects should not have any direct interaction. If one of the objects needs to call a method of another object, the call can be forwarded through a third party. In short, it is to reduce the coupling between existing objects by introducing a reasonable third party. When applying the Demeter principle to the system design, the following points should be paid attention to:

  • In terms of class division, loosely coupled classes should be created as far as possible. The lower the coupling between classes, the more conducive to reuse. Once a class in loose coupling is modified, it will not cause too much damage to the associated class.
  • In terms of class structure, each class should minimize the access rights of its member variables and member functions.
  • In terms of references to other classes, the references of one object to other objects should be minimized.

for instance

Just like renting a house, my name is Lao Wang. I'm going to rent a house, and then find an intermediary. The intermediary talks about the price with the landlord, and we talk about the price with the intermediary. If we want to contact the landlord ourselves, this kind of thing must be introduced through the intermediary or other ways.

The Demo is as follows

//Tenant: old king
//Information notification method
class LaoWang {
    private Zhongjie zhongjie;

    public Zhongjie getZhongjie(){
        return zhongjie;
    }

    public void setZhongjie(Zhongjie zhongjie) {
        this.zhongjie = zhongjie;
    }
    public void inform(){
        zhongjie.inform();
    }
}

//Intermediary class
//Information notification method
//setLandlord receives landlord messages
class Zhongjie{
    public void inform(){
        System.out.println("Inform the landlord");
    }
    public   void getLanged(){
        new Landlord().inform();
    }
}

//Landlord class
//Information notification method
class Landlord{
    public void inform(){
        System.out.println("Yes, you can rent it");
    }
}

class Renting{
    public static void main(String[] args) {
        LaoWang laoWang=new LaoWang();
        //Lao Wang informs the intermediary
        laoWang.inform();
        //Incoming landlord object
        laoWang.setZhongjie(new Zhongjie());
        //The intermediary will inform the landlord
        laoWang.getZhongjie().inform();
        //Receive landlord messages
        laoWang.getZhongjie().getLanged();
    }
} 

In this way, there is no connection between Lao Wang and the landlord, avoiding too high coupling.

We can also change the above Demo again, which is quite combined with the dependency inversion principle. Because generally renting a house will see many houses, so the landlords are also different. At this time, the landlords can be drawn into an abstract class. The specific landlords can implement the abstract method of the landlords. In this way, the abstract parent of the landlords communicates with Lao Wang, which has nothing to do with the specific landlords.

public abstract class Lease {
    abstract void inform();
}
 
//Renter: Lao Wang
//Information notification method
class LaoWang {
    private Zhongjie zhongjie;
    private Lease lease;
    
    public void setLease(Lease lease) {
        this.lease = lease;
    }
 
    public void setZhongjie(Zhongjie zhongjie) {
        this.zhongjie = zhongjie;
    }
 
    public void inform() {
        zhongjie.inform();
        lease.inform();
    }
}
 
//Intermediary class
//Information notification method
class Zhongjie {
    public void inform() {
        System.out.println("Inform the landlord");
    }
}
 
//Landlord_X landlord x
//Information notification method
class Landlord_X extends Lease {
    public void inform() {
        System.out.println("Yes, you can rent it");
    }
}
 
class Renting{
    public static void main(String[] args) {
        LaoWang laoWang=new LaoWang();
        laoWang.setLease(new Landlord_X());
        laoWang.setZhongjie(new Zhongjie());
        laoWang.inform();
    }
}

Interface isolation principle

The dependence of one class on another should be based on the smallest interface

Establish a single interface instead of a huge and bloated interface: refine the interface as much as possible and minimize the methods in the interface. In other words, we should establish special interfaces for each class instead of trying to establish a huge interface for all classes that depend on it. When adopting the interface isolation principle to restrict the interface, pay attention to the following points:

  • The interface should be as small as possible, but limited. Refining the interface can improve the flexibility of program design; However, if it is too small, it will cause too many interfaces and complicate the design. Therefore, we must be moderate.
  • Customizing services for classes that depend on interfaces only exposes the methods needed by the calling classes, while the methods they don't need are hidden. Only by focusing on providing customized services for a module can the minimum dependency be established.
  • In order to improve cohesion and reduce external interaction. Interface methods should be decorated with public as little as possible. Interface is an external commitment. The less commitment, the more favorable it is to the development of the system and the less risk of change.

Demo

//Color color
//taste
//hardness
// small odor
interface Frits {
    void color();
    void taste();
    void hardness();
    void small();
}
 class apple implements Frits{

     @Override
     public void color() {

     }

     @Override
     public void taste() {

     }

     @Override
     public void hardness() {

     }

     @Override
     public void small() {

     }
 }

A fruit interface is defined, which contains various methods of fruit. If we have other methods, this interface will be modified many times. Therefore, we can separate them. For example, the appearance is a class and the interior is a class. The results are as follows

//Facade appearance
interface Facade {
     void color();
     void hardness();
}
//Inherent intrinsic
interface Inherent {
    void taste();
    void small();
}
class banana implements Facade,Inherent{
    @Override
    public void taste() {
 
    }
 
    @Override
    public void small() {
 
    }
 
    @Override
    public void color() {
 
    }
 
    @Override
    public void hardness() {
 
    }
}

Interfaces are contracts provided externally during our design. By defining multiple interfaces in a decentralized manner, we can prevent the proliferation of future changes and improve the flexibility and maintainability of the system.