Seven principles of software design
The open closed principle
- Definition: A software entity such as a class, module, or function should be open for extension and closed for modification.
- Build frameworks with abstractions and extend details with implementations.
- Advantages: Improved software system reusability and maintainability.
Code sample
Interface:
public interface ICourse {
Integer getId();
String getName();
Double getPrice();
}
The base class:
public class JavaCourse implements ICourse{ private Integer id; private String name; private Double price; public JavaCourse(Integer id, String name, Double price) { this.id = id; this.name = name; this.price = price; } @Override public Integer getId() { return this.id; } @Override public String getName() { return this.name; } @Override public Double getPrice() { return this.price; } @Override public String toString() { return "JavaCourse{" + "id=" + id + ", name='" + name + '\'' + ", price=" + price + '}'; }}
Extended classes (subclasses) :
public class JavaDiscountCourse extends JavaCourse{ public JavaDiscountCourse(Integer id, String name, Double price) { super(id, name, price); } public Double getOriginPrice(){ return super.getPrice(); } public Double getPrice() {return super.getPrice()*0.8; }}
Testing:
public class OpenCloseTest { @Test public void openCloseTest(){ ICourse iCourse = new JavaDiscountCourse(99, "Java from Zero to Enterprise Development ", 348D); JavaDiscountCourse javaDiscountCourse = (JavaDiscountCourse) iCourse; System. Out.println (" course ID: "+ javaDiscountCourse. GetId () +" course name: "+ javaDiscountCourse. GetName () +" price: "+ javaDiscountCourse. GetPrice () +" initial price: "+ javaDiscountCourse. GetOriginPrice ()); }}
The principle of analysis
Application scenario: Discount sales
Demand: We need to add discount function of online courses on the website.
Implementation: directly extends (inherits) the base class at the “application layer”, rather than directly modifying the “underlying” interface or base class.
Reason: If there are more methods of the interface, the logic of the base class implementation is more complex, and direct modification of it will spread risks and easily cause bugs. The lower the level of module (or the more basic module) the change, the greater the impact; On the contrary, the higher the level of module modification, the smaller the scope of impact.
Example: If a module in the DAO layer is used by multiple Service layer modules, changes to the module in the DAO layer will affect multiple Service layer modules and even affect the Controller layer.
Rely on the inversion principle
- Definition: High-level modules should not depend on lower-level modules; both should depend on their abstractions.
- Abstractions should not rely on details; Details should rely on abstractions.
- Program to interfaces, not implementations.
- Advantages: It can reduce coupling between classes, improve system stability, improve code readability and maintainability, and reduce the risk of modifying programs.
Code sample
Entity class:
public class Feyl { private ICourse icourse; public void setICourse(ICourse icourse) { this.icourse = icourse; } public void studyCourse(){ icourse.studyCourse(); }}
Interface:
public interface ICourse {
void studyCourse();
}
Implementation class:
public class ArithmeticCourse implements ICourse{
@Override
public void studyCourse() {
System.out.println("Feyl is learning Arithmetic course!");
}
}
public class JavaCourse implements ICourse{
@Override
public void studyCourse() {
System.out.println("Feyl is learning Java course!");
}
}
public class FECourse implements ICourse{
@Override
public void studyCourse() {
System.out.println("Feyl is learning FECourse!");
}
}
The test class:
public class DependencyInversionTest { @Test public void DependencyInversionTest(){ Feyl feyl = new Feyl(); feyl.setICourse(new ArithmeticCourse()); feyl.studyCourse(); feyl.setICourse(new JavaCourse()); feyl.studyCourse(); feyl.setICourse(new FECourse()); feyl.studyCourse(); }}
The principle of analysis
Application scenario: Add learning courses
Requirement: to increase the course function of entity class (human) learning.
Implement: Relate entity classes (people) to specific courses through the iCourse interface. When adding a specific course, let the added course directly implement the interface, this process does not need to modify the specific entity class (people).
Single Responsibility Principle
- Definition: There should not be more than one reason for a class to change
- A class/interface/method is responsible for only one responsibility
- Benefits: Reduced class complexity, improved class readability, improved system maintainability, reduced risk caused by change
Code sample
The single responsibility of the class:
public class ICourse {
}
public interface ICourseManager {
void studyCourse();
void refundCourse();
}
public interface ICourseContent {
String courseName();
byte[] courseVideo();
}
public class ICourseImpl implements ICourseManager,ICourseContent{
@Override
public String courseName() {
return null;
}
@Override
public byte[] courseVideo() {
return new byte[0];
}
@Override
public void studyCourse() {
}
@Override
public void refundCourse() {
}
}
A single responsibility for the method:
public class Method { public void updateUserInfo(String username, String address){ username = "Feyl"; address = "HeNan"; } public void updateUserInfo(String username, String... properties){ username = "Feyl"; // properties = ; } public void updateUsername(String username){ username = "Feyl"; } public void updateAddress(String address){ address = "HeNan"; }}
Interface isolation principle
- Definition: With multiple specialized interfaces rather than a single master interface, a client should not rely on interfaces it does not need
- A class’s dependency on a class should be based on the smallest interface
- Build a single interface, not a bloated one
- Minimize the interface and minimize the number of methods in the interface
- Pay attention to the principle of moderation, must be moderate
- Advantages: It conforms to what we call the design concept of high cohesion and low coupling, which makes the class readable, extensible, and maintainable.
Code sample
Example:
public interface IAnimalAction {
void eat();
void fly();
void swim();
}
public class Bird implements IAnimalAction{
@Override
public void eat() {
}
@Override
public void fly() {
}
@Override
public void swim() {
}
}
Example:
public interface IEatAnimalAction {
void eat();
}
public interface IFlyAnimalAction {
void fly();
}
public interface ISwimAnimalAction {
void swim();
}
public class Dog implements ISwimAnimalAction,IEatAnimalAction{
@Override
public void eat() {
}
@Override
public void swim() {
}
}
The Demeter Principle
- Definition: An object should have minimal knowledge of other objects. Also known as the least known principle
- Minimize coupling between classes
- Advantage: Reduced coupling between classes
- Emphasize talking to friends and not to strangers
- Friends: Classes that appear in the input and output parameters of a member variable or method are called member friends. Classes that appear in the body of a method are not friends.
Code sample
Example:
public class Course {}
Public class TeamLeader {public void checkNumberOfCourse(List<Course> Courses){System.out.println(); "+courses.size()); }}
public class Boss { void commandCheckNumber(TeamLeader teamLeader) { List<Course> list = new ArrayList<Course>(); for (int i = 0; i < 20; i++) { list.add(new Course()); } teamLeader.checkNumberOfCourse(list); }}
The test class:
public class DemeterTest { @Test public void demeterTest() { new Boss().commandCheckNumber(new TeamLeader()); }}
Example:
public class TeamLeader { public void checkNumberOfCourse(){ List<Course> courses = new ArrayList<Course>(); for (int i = 0; i < 20; i++) { courses.add(new Course()); } System.out.println(" Number of online courses: "+courses.size()); }}
public class Boss { public void commandCheckNumber(TeamLeader teamLeader) { teamLeader.checkNumberOfCourse(); }}
The Richter principle of substitution
Definition: Type T2 is a subtype of type T1 if for every object o1 of type T1, there is an object o2 of type T2, such that the behavior of program P defined in T1 does not change when all objects o1 are replaced with O2.
Defining extensions: If a software entity applies to a superclass, it must apply to its subclasses. All references to the superclass must transparently use objects of the subclass. Subclass objects can replace the superclass objects without changing the program logic.
Extended meaning: a subclass can extend the function of the superclass, but it cannot change the original function of the superclass.
Meaning 1: A subclass can implement abstract methods of its parent class, but it cannot override nonabstract methods of its parent class.
Meaning 2: Subclasses can add their own unique methods.
Meaning 3: When a subclass’s method overrides a superclass’s method, the method’s preconditions (the method’s input/input arguments) are looser than the input parameters of the superclass’s method.
Meaning 4: When a method of a subclass implements a method of the superclass (overriding/overriding or implementing an abstract method), the postconditions of the method (that is, the output/return value of the method) are stricter or equal than those of the superclass.
Advantage 1: the overflow of constraint inheritance, a manifestation of the Open Closed Principle.
Advantage 2: strengthen the robustness of the program, while the change can also do very good compatibility, improve the maintainability, expansibility of the program. Reduce risks introduced when requirements change.
Modify the code of the Open Closed Principle to make it conform to the Richter Replacement Principle:
// The ICourse interface and JavaCourse remain the same
public class JavaDiscountCourse extends JavaCourse{ public JavaDiscountCourse(Integer id, String name, Double price) { super(id, name, price); } public Double getDiscountPrice(){return super.getPrice()*0.8; }}
The test class:
public class OpenCloseTest { @Test public void openCloseTest(){ ICourse iCourse = new JavaDiscountCourse(99, "Java from Zero to Enterprise Development ", 348D); JavaDiscountCourse javaDiscountCourse = (JavaDiscountCourse) iCourse; System. Out.println (" course ID: "+ javaDiscountCourse. GetId () +" course name: "+ javaDiscountCourse. GetName () +" price: "+ javaDiscountCourse. GetDiscountPrice () +" initial price: "+ javaDiscountCourse. GetPrice ()); }}
Code sample
Example:
public class Rectangle {
private long length;
public long getLength() {
return length;
}
public void setLength(long length) {
this.length = length;
}
public long getWidth() {
return width;
}
public void setWidth(long width) {
this.width = width;
}
private long width;
}
public class Square extends Rectangle{ private long sideLength; public long getSideLength() { return sideLength; } public void setSideLength(long sideLength) { this.sideLength = sideLength; } @Override public long getLength() { return getSideLength(); } @Override public void setLength(long length) { setSideLength(length); } @Override public long getWidth() { return getSideLength(); } @Override public void setWidth(long width) { setSideLength(width); }}
The test class:
public class Test { public static void resize(Rectangle rectangle){ while(rectangle.getWidth() <= rectangle.getLength()){ rectangle.setWidth(rectangle.getWidth()+1); System.out.println("Width: " + rectangle.getWidth()+"\t Length: "+rectangle.getLength()); } System.out.println(" Width: "+ Rectangle.getWidth()+"\tLength: "+ Rectangle.getLength()); } public static void main(String[] args) { /*Rectangle rectangle = new Rectangle(); rectangle.setWidth(10); rectangle.setLength(20); resize(rectangle); */ Square square = new Square(); square.setLength(10); resize(square); }}
Example:
public interface Quadrangle {
long getWidth();
long getLength();
}
public class Rectangle implements Quadrangle{ private long width; private long length; @Override public long getWidth() { return 0; } @Override public long getLength() { return 0; } public void setWidth(long width) { this.width = width; } public void setLength(long length) { this.length = length; }}
public class Square implements Quadrangle{ private long sideLength; @Override public long getWidth() { return sideLength; } @Override public long getLength() { return sideLength; } public long getSideLength() { return sideLength; } public void setSideLength(long sideLength) { this.sideLength = sideLength; }}
Test // square.setLength(10); // resize(square); Exceptions are thrown. If you change resize(Rectangle) to resize(QuadRangle QuadRangle), the set method in the method also fails. From this point of view, the inheritance relation in the counter example violates the Richter substitution principle.
Method overloading (input parameter) :
Public class Base {public void method(HashMap Map){System.out.println(" HashMap of the parent class is executed "); ); }}
public class Child extends Base { /* @Override public void method(HashMap map) { System.out.println(" The subclass's HashMap input parameter method was called!" ); }*/ public void method(Map){System.out.println();}*/ public void method(Map){System.out.println(); ); }}
Method overloading (return value) :
public abstract class Base {
public abstract Map method();
}
public class Child extends Base{ @Override public HashMap method() { HashMap<String,String> hashMap = new HashMap<>(); System.out.println(" HashMap of subclass is called!") ); HashMap. put(" MSG "," subclass HashMap called!" ); return hashMap; }}
Composition (composition)/ aggregation reuse principle
- Definition: use object composition/aggregation instead of inheritance for software reuse purposes
- The aggregate has-A and the composition contains-A
- Advantages: Can make the system more flexible, reduce the degree of coupling between classes, the change of one class has relatively less impact on other classes
- When to use composition/aggregation, inheritance
- The aggregate has-A and the composition contains-A inherit from IS-A
Code sample
Public abstract class dbConnection {/*public String getConnection(){return "MySQL "; }*/ public abstract String getDBConnection(); }
Public class MySQLConnection extends DBConnection{@Override public String getDBConnection() {return "MySQL Connection "; }}
public class PostgreSQLConnection extends DBConnection{ @Override public String getDBConnection() { return "PostgreSQL database connection "; }}
public class ProductDao{ private DBConnection dbConnection; public void setDbConnection(DBConnection dbConnection) { this.dbConnection = dbConnection; } public void addProduct(){ String conn = dbConnection.getDBConnection(); System.out.println(" use "+conn+" add product "); }}
Test code:
public class CompositionAggregationTest { @Test public void compositionAggregationTest(){ ProductDao productDao = new ProductDao(); // productDao.setDbConnection(new MySQLConnection()); productDao.setDbConnection(new PostgreSQLConnection()); productDao.addProduct(); }}