Seven design principles
Single responsibility
A single responsibility is that each method is responsible for one thing. Reduce the coupling and encapsulate the method.
public class Test2 { public static String loadFile(String fileName) throws IOException { // FileReader reader = new FileReader("D:\Bilysen Java\idea\java design pattern \ \ src\seven_principles\data\4.txt"); FileReader reader = new FileReader(fileName); BufferedReader br = new BufferedReader(reader); String line = null; StringBuffer sb = new StringBuffer(); while ((line = br.readLine())!=null){ sb.append(line); sb.append(" "); System.out.println(line); } br.close(); reader.close(); return sb.toString(); } public static int getWords(String s,String re){ int count; String[] strings = s.split(re); // for (String string : strings) { // System.out.println(string+"~~~"+string.length()); // } if(strings[strings.length-1].trim().length()==0){ count = strings.length-1; // System.out.println(strings.length-1); }else { count = strings.length; // System.out.println(strings.length); } return count; } public static void main(String[] args) throws IOException { // After encapsulating the codes of these two places, users directly call their methods without knowing the details. Each method is only responsible for one piece of business and reduces the coupling degree String s = loadFile("D:\\Bilysen Java\\idea\\java Design pattern\\src\\seven_principles\\data\\3.txt"); // System.out.println(getWords(s, "[!.. ?]+")); System.out.println(getWords(s,"[^a-zA-Z]+")); // String[] strings = sb.toString().split("[^a-zA-Z]+"); } }
Richter substitution principle
Before we understand the Richter substitution principle, we need to understand rewriting and overloading
Overloading refers to the realization of multiple availability in a method name. It is characterized by the same method name and different return values or parameters. This can distinguish two methods with the same name.
Rewriting refers to the implementation of overriding in the methods that the subclass allows access to the parent class. It is a method with the same return type, the same method name and the same method parameters. It aims to rewrite the method of the parent class and rewrite the code in the method of the parent class to achieve the desired effect.
Overridden rule:
- The parameter list must be exactly the same as the parameter list of the overridden method.
- The return value can be different, but it must be a derived class (subclass) that overrides the return value of the method
- When a subclass overrides a parent class method, the access modifier of the subclass method cannot be more strict than the parent class. If the access modifier of the parent class method is public, the child class cannot be protected
- When a subclass overrides a parent method, the subclass method cannot throw more exceptions than the parent
- Methods declared final cannot be overridden.
- Methods declared as static cannot be overridden, but can be declared (used) again.
- If the subclass and the parent class are in the same package, the subclass can override all methods of the parent class, except those declared as private and final.
- If the subclass and the parent class are not in the same package, the subclass can only override the non final methods declared as public and protected by the parent class.
- Constructor cannot be overridden.
The Richter substitution principle means that a subclass can extend the functions of the parent class, but cannot change the original functions of the parent class.
Now there is a requirement: make a method to accept a rectangle to modify the width and length of the rectangle. As long as it is found that the width is smaller than the length, let the width accumulate by 1 until the width just exceeds the length.
- Counterexample:
There is an "is a" relationship between a square and a rectangle. See whether the business logic changes after the square replaces the rectangle in a specific business scenario.
However, the following example shows that although the object received by this method is a rectangular object, the square object inherits the rectangular class, which will lead to the upward transformation of the incoming square class, resulting in an endless loop. Did not achieve the desired effect.
class Utils{ static void transfrom(Rectangle r){ while (r.getWidth()<=r.getHeight()){ r.setWidth(r.getWidth()+1); System.out.println(r.getWidth()+":"+r.getHeight()); } } } class Rectangle{ private double width; private double height; public double getWidth() { return width; } public void setWidth(double width) { this.width = width; } public double getHeight() { return height; } public void setHeight(double height) { this.height = height; } } class Square extends Rectangle{ private double side; @Override public double getWidth() { return this.side; } @Override public double getHeight() { return this.side; } @Override public void setWidth(double width) { this.side = width; } @Override public void setHeight(double height) { this.side= height; } } public class Test2 { public static void main(String[] args) { Rectangle rectangle = new Square(); rectangle.setWidth(12); rectangle.setHeight(20); Utils.transfrom(rectangle); } }
- Positive example
// To solve the problem in the counterexample, we must break the inheritance relationship between Square and Rectangle // Because in such a business scenario, Square cannot be regarded as a Rectangle type! // In other words, there is no "is a" relationship between Square and Rectangle! interface Quadrangle { double getWidth(); double getLength(); } class Rectangle implements Quadrangle { private double width; private double length; public double getWidth() { return width; } public double getLength() { return length; } public void setWidth(double width) { this.width = width; } public void setLength(double length) { this.length = length; } } class Square implements Quadrangle { private double sideLength; @Override public double getWidth() { return sideLength; } @Override public double getLength() { return sideLength; } public void setSideLength(double sideLength) { this.sideLength = sideLength; } } // According to the requirements, make a method to accept a rectangle to modify the width and length of the rectangle, // As long as it is found that the width is smaller than the length, let the width accumulate by 1 until the width just exceeds the length. public class Test { public static void tranfrom(Rectangle r) { while(r.getWidth() <= r.getLength()) { r.setWidth(r.getWidth() + 1); System.out.println("width:" + r.getWidth() + ", " + r.getLength()); } } public static void main(String[] args) { Square s = new Square(); Rectangle r = new Rectangle(); tranfrom(r); } }
Opening and closing principle
The opening and closing principle refers to opening to expansion and closing to modification. This means that an entity is allowed to change its behavior without changing its source code. This is the most important principle in design patterns, which must be followed in many cases.
Two concepts: author and user. The author refers to the architecture and writer of this generation of code. User refers to calling code and using it directly.
The opening and closing principle should be considered according to the application scenario. If you write the source code yourself and the requirements are stable, it is also a simple way to modify the source code directly.
But when the source code is someone else's code or architecture, we have to comply with the opening and closing principle to prevent damaging the integrity of the structure and leading to unpredictable errors!
There is a business that needs to display information about selling cars. However, after a period of time, changes have come. The company hopes to make a big reward and discount the vehicles.
Counter example: directly multiply the corresponding discount in setPrice in car class. This obviously violates the opening and closing principle. I may not have written the source code. Secondly, the price of this standard may be related to other functions and businesses. Changing it directly here is likely to affect the normal use of other businesses.
Positive example: create a discount Car class, inherit the parent class Car, and override the setPrice method.
public class Car { private String name; private double price; public String getName() { return name; } public void setName(String name) { this.name = name; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } @Override public String toString() { return "Car{" + "name='" + name + '\'' + ", price=" + price + '}'; } }
public class DiscountCar extends Car{ @Override public void setPrice(double price) { super.setPrice(price*0.8); } }
Interface isolation principle
When designing interfaces, they should be abstract and meaningful.
The client should not rely on interfaces that it does not need. Otherwise, the interface is too bloated.
Once an interface is too large, it needs to be divided into smaller interfaces. The client using the interface only needs to know the related methods.
Dependency Inversion Principle
Code should rely on abstract classes rather than concrete classes; Program for interfaces or abstract classes, not specific classes. Through interface oriented programming, abstraction should not depend on details, and details should depend on abstraction.
There is a business. A man has a dog and needs to feed the dog. However, changes have come. People have another cat and need to feed the cat.
Counterexample: the method of overloading feed directly in People obviously violates the opening and closing principle. Any change needs to change the upper level.
public class test1 { //It is added by overloading, but it violates the opening and closing principle static class People{ public void feed(Cat cat){ cat.feed(); } public void feed(Dog dog){ dog.feed(); } } /*=========================Increase business demand===========================================*/ static class Cat{ public void feed(){ System.out.println("Cats eat fish"); } } static class Dog{ public void feed(){ System.out.println("Dogs eat bones"); } } public static void main(String[] args) { People people = new People(); Cat cat = new Cat(); people.feed(cat); Dog dog = new Dog(); people.feed(dog); } }
The uml diagram at this time is like this. The upper layer directly depends on specific classes, which leads to the need to modify the source code whenever the business changes.
Positive example: the author creates an interface in which the implementation method is written. When the user wants to call, first implement the interface and implement the methods in the interface. In this way, the problem of opening and closing principle can be solved. The upper layer depends on the interface, and the specific class implements the interface.
public class test1 { static class People{ public void feed(Animal animal){ animal.feed(); } } interface Animal{ void feed(); } /*=========================Increase business demand===========================================*/ static class Cat implements Animal{ public void feed(){ System.out.println("Cats eat fish"); } } static class Dog implements Animal{ public void feed(){ System.out.println("Dogs eat bones"); } } public static void main(String[] args) { People people = new People(); people.feed(new Cat()); people.feed(new Dog()); } }
uml diagram
Dimitt's law
Dimitri's law is also known as the least known principle. For one class, for other classes, the less you know, the better.
There is a business, shut down the computer
Counterexample: the Person class needs to know too much. You should also know the steps to shut down the computer.
class Computer{ public void saveData(){ System.out.println("Save data"); } public void killProcess(){ System.out.println("close program"); } public void closeScreen(){ System.out.println("Close screen"); } public void powerOff(){ System.out.println("power failure"); } } class Person{ private Computer c; //Users know too much about computer details public void shutdown(){ c.saveData(); c.killProcess(); c.closeScreen(); c.powerOff(); } }
Positive example: provide a public method to encapsulate the shutdown process. The user can know which method is shutdown.
class Computer{ private void saveData(){ System.out.println("Save data"); } private void killProcess(){ System.out.println("close program"); } private void closeScreen(){ System.out.println("Close screen"); } private void powerOff(){ System.out.println("power failure"); } public void shutdown(){ saveData(); killProcess(); closeScreen(); powerOff(); } } class Person{ private Computer c; //Users know too much about computer details public void shutdown(){ c.shutdown(); } }
Combination is better than inheritance
Before learning that composition is better than inheritance, we must clarify several concepts.
- Inheritance: one class inherits from another
- Dependency: an object of one class as a local variable of another class's method
- Association: one class knows the properties and methods of another class
There is a business that makes a collection and asks how many elements have been added to the collection so far
Package a: inherit HashSet and override the add() and addAll() methods.
Problem: the add method is called back in addAll, resulting in data error
class MySet extends HashSet{ private int count; public int getCount() { return count; } @Override public boolean add(Object o) { count++; return super.add(o); } @Override public boolean addAll(Collection c) { count+=c.size(); return super.addAll(c); } } public class Test { public static void main(String[] args) { MySet mySet = new MySet(); mySet.add(1); mySet.add(1); mySet.add(1); HashSet hashSet = new HashSet(); hashSet.add("1"); hashSet.add("2"); mySet.addAll(hashSet); System.out.println(mySet.getCount()); } }
Package b: for the problem of package a, because the add method is called back at the bottom of addAll. Solution: do not rewrite the addAll method
Problem: in the future jdk version, the bottom addAll will no longer call the add method, and the method we write will make an error.
class MySet extends HashSet{ private int count; public int getCount() { return count; } @Override public boolean add(Object o) { count++; return super.add(o); } } public class Test { public static void main(String[] args) { MySet mySet = new MySet(); mySet.add(1); mySet.add(1); mySet.add(1); HashSet hashSet = new HashSet(); hashSet.add("1"); hashSet.add("2"); mySet.addAll(hashSet); System.out.println(mySet.getCount()); } }
Package c: we rewrite addAll ourselves
Question:
1. If a new addSome() method is added in the new version of jdk, we must override this method.
2. In rewriting hashset methods, it is inevitable that other methods will depend on the methods in hashset, and our rewriting will inevitably risk the collapse of the structure written by the author.
class MySet extends HashSet{ private int count; public int getCount() { return count; } @Override public boolean add(Object o) { count++; return super.add(o); } @Override public boolean addAll(Collection c) { boolean flag = false; for (Object o : c) { if (add(o)){ flag = true; } } return flag; } } public class Test { public static void main(String[] args) { MySet mySet = new MySet(); mySet.add(1); mySet.add(1); mySet.add(1); HashSet hashSet = new HashSet(); hashSet.add("1"); hashSet.add("2"); mySet.addAll(hashSet); System.out.println(mySet.getCount()); } }
d package: when the source code author is not us, we should adopt the combination method. The add and addAll methods in our class have no relationship with the add and addAll methods in HashSet, which can also solve this problem
class MySet{ //combination private Set set = new HashSet(); private int count; public int getCount() { return count; } public boolean add(int i) { count++; return set.add(i); } public boolean addAll(Collection c) { count=count+c.size(); return set.addAll(c); } } public class Test { public static void main(String[] args) { MySet mySet = new MySet(); mySet.add(1); mySet.add(1); mySet.add(1); HashSet hashSet = new HashSet(); hashSet.add("1"); hashSet.add("2"); mySet.addAll(hashSet); System.out.println(mySet.getCount()); } }