This is the 10th day of my participation in the August More Text Challenge.

Write study notes on design patterns and design principles in this series

Design Pattern #1 (7 Design Principles)


Single responsibility principle

Brief description: A single class, method, or framework performs a specific function.

Requirement: Count how many words are in a text file.

Example:

Copypublic class nagtive {
    public static void main(String[] args) {
        try{
            // Read the contents of the file
            Reader in = new FileReader("E:\\1.txt");
            BufferedReader bufferedReader = new BufferedReader(in);

            String line = null;
            StringBuilder sb = new StringBuilder("");

            while((line =bufferedReader.readLine()) ! =null){
                sb.append(line);
                sb.append("");
            }

            // Split the content
            String[] words = sb.toString().split("[^a-zA-Z]+");
            System.out.println(words.length);

            bufferedReader.close();

        } catch(IOException e) { e.printStackTrace(); }}}Copy the code

The above code violates the single responsibility principle, the same method we let it do file reading, also let it do content segmentation; When there is a requirement change (need to change the load file, count how many sentences are in the text file), we need to rewrite the entire method.

Is:

Copypublic class postive {
    
    public static StringBuilder loadFile(String fileLocation) throws IOException {
      
            // Read the contents of the file
            Reader in = new FileReader("E:\\1.txt");
            BufferedReader bufferedReader = new BufferedReader(in);

            String line = null;
            StringBuilder sb = new StringBuilder("");

            while((line = bufferedReader.readLine()) ! =null) {
                sb.append(line);
                sb.append("");
            }
            
            bufferedReader.close();
            return sb;
    }
    
    public static String[] getWords(String regex, StringBuilder sb){
        // Split the content
        return  sb.toString().split(regex);
    }
    
    public static void main(String[] args) throws IOException {
        
            // Read the contents of the file
            StringBuilder sb = loadFile("E:\\1.txt");
            
            // Split the content
            String[] words = getWords("[^a-zA-Z]+", sb); System.out.println(words.length); }}Copy the code

The benefit of adhering to the single principle is that it makes our code more reusable, while also removing the resulting data from the coupling that can be used to fulfill our personalized needs.

The open closed principle

Description: Open for extensions (new features), closed for modifications (old features)

Entity class design:

Copypublic class Pen {
    private String prod_name;
    private String prod_origin;
    private float prod_price;

    public String getProd_name(a) {
        return prod_name;
    }

    public void setProd_name(String prod_name) {
        this.prod_name = prod_name;
    }

    public String getProd_origin(a) {
        return prod_origin;
    }

    public void setProd_origin(String prod_origin) {
        this.prod_origin = prod_origin;
    }

    public float getProd_price(a) {
        return prod_price;
    }

    public void setProd_price(float prod_price) {
        this.prod_price = prod_price;
    }

    @Override
    public String toString(a) {
        return "Pen{" +
                "prod_name='" + prod_name + '\' ' +
                ", prod_origin='" + prod_origin + '\' ' +
                ", prod_price=" + prod_price +
                '} '; }}Copypublic static void main(String[] args) {
        // Enter the commodity information
        Pen redPen = new Pen();
        redPen.setProd_name("Hero pen");
        redPen.setProd_origin("The factory");
        redPen.setProd_price(15.5 f);
        // Output commodity information
        System.out.println(redPen);
    }
Copy the code

Demand: merchandise activities, 20% discount sales.

Counterexample: In the entity class source code, modify the setProd_price method

Copypublic void setProd_price(float prod_price) {
        this.prod_price = prod_price * 0.8 f;
    }
Copy the code

Violated the open and close principle, modified in the source code, the display of the original price of this function has been modified.

In the development, we should, must consider the requirements may change, attributes may change at any time, for the change of requirements, in accordance with the open and closed principle of the premise, we should be in the development to expand, rather than modify the source code.

Is:

Copypublic class discountPen extends Pen{
	// Set the price with the override method
    @Override
    public void setProd_price(float prod_price) {
        super.setProd_price(prod_price * 0.8 f);
    }
}
Copypublic class postive {
    public static void main(String[] args) {
        // Enter the commodity information and call the overwrite method to set the price
        Pen redPen = new discountPen();
        redPen.setProd_name("Hero pen");
        redPen.setProd_origin("The factory");
        redPen.setProd_price(15.5 f);
        // Output commodity informationSystem.out.println(redPen); }}Copy the code

Open and close principle is not necessarily to blindly adhere to, need to be combined with the development of the scene to use, if the need to modify the source code is written, modify to complete the requirements, of course, is simple and fast; But if the source code is written by someone else, or if it is someone else’s architecture, there is a huge risk of modification, and the open closed principle should be followed to avoid damaging the integrity of the structure.

Interface Isolation Principle

Description: When designing an interface, the abstraction of the interface should be meaningful. What needs to be designed is a cohesive, single-responsibility interface. “It’s better to have multiple specialized interfaces than a single master interface.” This principle does not encourage the design of “universal” interfaces.

Counterexample: Animal interfaces are not needed by all animals.

Copypublic interface Animal {
    void eat(a);
    void fiy(a); // Loach: You fly?
    void swim(a); // Big Eagle: You come to swim?
}
Copyclass Bird implements Animal {
    @Override
    public void eat(a) {
        System.out.println("Eat with your mouth");
    }

    @Override
    public void fiy(a) {
        System.out.println("Fly with wings.");
    }

    @Override
    public void swim(a) {
    // I can't swim}}Copy the code

The Swim () method of the interface does not apply to this class in practical development.

Example: An interface abstracts a specific level of meaning from the same level and provides it to the required classes to implement.

Copypublic interface Fly {
    void fly(a);
}

public interface Eat {
    void eat(a);
}

public interface Swim {
    void swim(a);
}
Copypublic class Bird_02 implements Fly.Eat{
    @Override
    public void eat(a) {
        System.out.println("Eat with your mouth");
    }

    @Override
    public void fly(a) {
        System.out.println("Fly with wings.");
    }

    // I can't swim
}
Copy the code

There should be no methods in an interface that a client depends on that he doesn’t need.

If an interface is too large for this to happen, the interface should be split so that the client using the interface only needs to know what interface he needs to use and the methods in that interface.

Dependency inversion principle

Brief description: upper levels cannot depend on lower levels, they should all depend on abstractions.

Needs: Humans feed animals

Example:

Copypublic class negtive {

    static class Person {
        public void feed(Dog dog){ dog.eat(); }}static class Dog {
        public void eat(a) {
            System.out.println("Master fed me. Auf..."); }}public static void main(String[] args) {
        Person person= new Person();
        Dog dog = newDog(); person.feed(dog); }}Copy the code

At this point, the feed method inside Person relies on Dog, and on a lower class within a higher method. A man is dependent on a dog? Is that a curse?)

When the requirements change and people have pets that are not just dogs, but cats, etc., the upper class needs to be modified, which introduces reusability issues and violates the open and close principle mentioned above.

Is:

Copypublic class postive {
    static class Person {
        public void feed(Animal animal){ animal.eat(); }}interface Animal{
        public void eat(a);
    }

    static class Dog implements Animal{
        public void eat(a) {
            System.out.println("I'm a dog. My master fed me. Auf..."); }}static class Cat implements Animal{
        public void eat(a) {
            System.out.println("I'm a cat, and my master fed me. (Why did I say yes?) Meow meow meow..."); }}public static void main(String[] args) {
        Person person= new Person();
        Dog dog = new Dog();
        Cat cat = newCat(); person.feed(dog); person.feed(cat); }}Copy the code

At this point, the feed method inside Person doesn’t rely on Dog or Cat, but either Person or Dog or Cat, they all rely on the abstract class Animal, they all rely on abstract classes.

At this point, neither the old upper code nor the old lower code will change because of the requirements.

The dependency inversion principle states that code should depend on abstract classes, not concrete ones. Program for interfaces or abstract classes, not concrete classes. With interface oriented programming, abstractions should not depend on details; details should depend on abstractions.

Demeter’s Rule (least Know rule)

Description: The less a class knows about other classes, the better. That is, an object should know as little as possible about other objects, and only communicate with friends, not strangers.

Example:

Copypublic class negtive {
    class Computer{
        public  void  closeFile(a){
            System.out.println("Close file");
        }
        public  void  closeScreen(a){
            System.out.println("Close the screen");
        }
        public  void  powerOff(a){
            System.out.println("Power"); }}class Person{
        private Computer computer;

        public void offComputer(a){ computer.closeFile(); computer.closeScreen(); computer.powerOff(); }}}Copy the code

At this time, Person knows a lot of details about the Computer, which is not friendly to users. Moreover, users may call an error, power off first and then save the file, which is obviously not logical and will lead to the error of unsaved files.

In fact, for users, know to shut down on the line.

Example: Encapsulate details

Copypublic class postive {
    class Computer{
        public  void  closeFile(a){
            System.out.println("Close file");
        }
        public  void  closeScreen(a){
            System.out.println("Close the screen");
        }
        public  void  powerOff(a){
            System.out.println("Power");
        }
        
        public void turnOff(a){  // Encapsulate details
            this.closeFile();
            this.closeScreen();
            this.powerOff(); }}class Person{
        private Computer computer;

        public void offComputer(a){ computer.turnOff(); }}}Copy the code

As mentioned earlier, only write to friends, not strangers. Let’s be clear about what a friend is:

What is a friend?

  1. Fields in a class
  2. Method return value
  3. Method parameters
  4. Method
  5. The object itself
  6. Generics in the collection

In general, as long as in their own definition is a friend, through other methods are just friends of friends;

But a friend of a friend is not a friend of mine.

Here’s a counter example:

Copypublic class negtive {

     class Market{
        private  Computer computer;
        public Computer getComputer(a){
            return this.computer; }}static class Computer{
        public  void  closeFile(a){
            System.out.println("Close file");
        }
        public  void  closeScreen(a){
            System.out.println("Close the screen");
        }
        public  void  powerOff(a){
            System.out.println("Power"); }}class Person{
        private Market market;

        Computer computer =market.getComputer(); 
        // // At this time, computer is not Person's friend, but Market's friend.}}Copy the code

In real development, there are drawbacks to fully complying with Demeter’s law:

  • Make a lot of small methods in the system that just pass indirect calls and have nothing to do with the business logic of the system.
  • Following Demeter’s rule between classes would be a local design simplification of a system, since each part would not have a direct connection to distant objects. However, this will also reduce the communication efficiency between different modules of the system, and make it difficult to coordinate between different modules of the system.

Therefore, some methodologies have been summarized by predecessors for our reference:

  1. Making a class immutable is a priority.
  2. Minimize access to a class.
  3. Use cautionSerializable.
  4. Minimize the access rights of members.

Although there are many rules, but the revolutionary theory needs a deep understanding, the actual combat needs to accumulate experience. There is a long way to go.

Richter’s substitution principle

Summary: Wherever a parent object can be used, it should be transparently replaced with a subclass object.

Need: Change the width of the rectangle to 1 ratio length.

Counterexample: Under the parent class Rectangular, the business scenario is logical. Existing subclass Square, how to replace.

Copypublic class negtive {
    static class Rectangular {
        private Integer width;
        private Integer length;

        public Integer getWidth(a) {
            return width;
        }

        public void setWidth(Integer width) {
            this.width = width;
        }

        public Integer getLength(a) {
            return length;
        }

        public void setLength(Integer length) {
            this.length = length; }}static class Square extends Rectangular {
        private Integer sideWidth;

        @Override
        public Integer getWidth(a) {
            return sideWidth;
        }

        @Override
        public void setWidth(Integer width) {
            this.sideWidth = width;
        }

        @Override
        public Integer getLength(a) {
            return sideWidth;
        }

        @Override
        public void setLength(Integer length) {
            this.sideWidth = length; }}static class Utils{
        public static void transform(Rectangular graph){
            while ( graph.getWidth() <= graph.getLength() ){
                graph.setWidth(graph.getWidth() + 1);
                System.out.println("Long."+graph.getLength()+":" +
                        "Wide."+graph.getWidth()); }}}public static void main(String[] args) {
    // Rectangular graph = new Rectangular();
        Rectangular graph = new Square();
        graph.setWidth(20);
       graph.setLength(30); Utils.transform(graph); }}Copy the code

The replacement will run in an infinite loop.

Keep in mind that when you transition up, the method calls are only related to the object of new and will result in different results. In use scenarios, you need to consider whether the business logic is affected after the replacement.

Thus, the following conditions should be considered in the application of Richter’s substitution principle:

  • Is there ais-aRelationship between
  • A subclass can extend the functionality of its parent class, but cannot change the functionality of its parent class.

There are many such counterexamples, such as the ostrich is not a bird, and our ancestors said long ago that the Spring and Autumn Period and the Warring States period — a white horse is not a horse.

Composition over Inheritance

Brief description: When reusing someone else’s code, use composition instead of inheritance.

Requirement: Make a composition that keeps track of how many elements have been added. (Not just a moment in time)

Example # 1:

Copypublic class negtive_1 {

    static class MySet extends HashSet{
        private int count = 0;

        public int getCount(a) {
            return count;
        }

        @Override
        public boolean add(Object o) {
            count++;
            return super.add(o); }}public static void main(String[] args) {
        MySet mySet = new MySet();
        mySet.add("111111");
        mySet.add("22222222222222");
        mySet.add("2333");


        Set hashSet = new HashSet();
        hashSet.add("Set + 11111");
        hashSet.add("Set + 22222222");
        hashSet.add("Set + 233333"); mySet.addAll(hashSet); System.out.println(mySet.getCount()); }}Copy the code

The add method can successfully increment the count by itself, and the addAll method can successfully increment the count by calling add inside the method.

Bug: If the JDK version is updated in the future and addAll no longer calls add from within the method, count will not be able to self-append when addAll is called to add elements to the collection. Demand will not be satisfied.

The HashMap was updated three times in 1.6, 1.7, and 1.8.


Example # 2:

Copypublic class negtive_2 {

    static class MySet extends HashSet{
        private int count = 0;

        public int getCount(a) {
            return count;
        }

        @Override
        public boolean add(Object o) {
            count++;
            return super.add(o);
        }

        @Override
        public boolean addAll(Collection c) {
            boolean modified = false;
            for (Object e : c)
                if (add(e))
                    modified = true;
            returnmodified; }}public static void main(String[] args) {
        MySet mySet = new MySet();
        mySet.add("111111");
        mySet.add("22222222222222");
        mySet.add("2333");


        Set hashSet = new HashSet();
        hashSet.add("Set + 11111");
        hashSet.add("Set + 22222222");
        hashSet.add("Set + 233333"); mySet.addAll(hashSet); System.out.println(mySet.getCount()); }}Copy the code

Override the addAll method to make sure that addAll calls to add and increment count.

But there are still problems:

Defect:

  • If in the future,HashSetThere’s a new oneaddSomeMethod to add elements, that’s nothing.
  • Rewrite theAddAll, addThese two methods, ifJDKSome methods of other classes in theHashMapThe two methods in, thenJDKThe other classes in theHashMapSome methods of the two methods are at risk of errors, crashes, and so on.

At this point, some conclusions can be drawn:

When we are not part of a development team that inherits the parent class, there is no way to guarantee that the parent code will not be changed, or that we will be notified when changes are made, which can lead to defective requirements. So, when we reuse the parent class code, we avoid rewriting or creating new methods to avoid the shock of changing the source code structure.

That said, we should use composition over inheritance when reusing code.

Is:

Copypublic class postive {
    
    static class MySet{
        private HashSet hashSet = new HashSet();

        private int count = 0;

        public int getCount(a) {
            return count;
        }

        public boolean add(Object o) {
            count++;
            return hashSet.add(o);
        }

        public boolean addAll(Collection c) {
            count += c.size();
            returnhashSet.addAll(c); }}public static void main(String[] args) {
        MySet mySet = new MySet();
        mySet.add("111111");
        mySet.add("22222222222222");
        mySet.add("2333");


        Set hashSet = new HashSet();
        hashSet.add("Set + 11111");
        hashSet.add("Set + 22222222");
        hashSet.add("Set + 233333"); mySet.addAll(hashSet); System.out.println(mySet.getCount()); }}Copy the code

Using combination to achieve decoupling, HashSet and custom class MySet are changed from the original inheritance relationship to a low coupling combination relationship.