0 x0, introduction

🤡 driver’s license, continue to bite “the beauty of design pattern”, this article is the design principles (15-22) condensed summary, the actual combat part (23-26) open to the next section, did not do Web development, points of time to digest.

Or that sentence: secondary knowledge processing inevitably has mistakes, interested in the time can consult the original text, thank you.


0x1. SOLID principle

It’s not just one principle, but it’s made up of the following five design Principles, with all the fun stuff you can always paste along from: SOLID Development Principles — In Pictures

â‘  SRP (Single Responsibility Principle)

A class or module is responsible for only one responsibility (or function), that is: don’t design large, complete classes, design small, single-purpose classes.

Here’s an example:

In a class that contains the order of operations, and contains the user’s operation, and orders, and users are two separate areas of the business model (DDD), put two unrelated functions in a class, is a violation of the single responsibility principle, the class can be split into smaller particle size, function more single two classes: order class and user classes.

But most of the time, it’s hard to tell whether a class has a single responsibility, such as:

public class UserInfo {
    private long userId;
    private String userName;
    private String avatarUrl;
    private String email;
    private String telephone; 
    private long createTime; 
    private long lastLoginTime; 
    private String provinceOfAddress; / / province
    private String cityOfAddress; / / the city
    private String regionOfAddress; / / area
    private String detailedAddress; // Full address
    / /... Omit other properties and methods
}
Copy the code

Two different views:

  • The UserInfo class contains all user-related information that belongs to the user’s business model and satisfies a single responsibility.
  • Address information is a high proportion of the class, you can split it into a separate UserAddress class, the two classes are more unified responsibility;

Which view is more true?

A: Depending on the application scenario, the current design is reasonable if the address information is just for presentation like other information.

If other functional modules also use the user’s address information, it is best to remove it.

Continuous refactoring:

Don’t start by thinking of breaking down the details. You can write a coarse-grained class to meet the business requirements. As the business develops, the coarse-grained class becomes larger and larger, and the code becomes more and more, then you can break the class into more fine-grained classes.

Here are some tips for determining if a class has a single responsibility:

  • Too many lines of code, functions or attributes in the class → will affect the readability and maintainability of the code, consider removing;
  • Too many other classes dependent on the class, or too many other classes dependent on the class → does not conform to high cohesion and low coupling, consider removing;
  • Too many private methods → consider whether to be independent into the new class, set as public method, for more classes to use, improve the reuse;
  • Difficulty in naming a class → difficulty in summarizing a business term, indicating that the responsibilities of a class may not be clearly defined;
  • < address > < address > < address > < address >

Is the class as thin as possible?

No, the single responsibility principle improves class cohesion and reduces code coupling by avoiding designing large, all-in-one classes and coupling unrelated functions together. But if you break it down too much, it can backfire, reducing cohesion and affecting the maintainability of your code.

Remember: The ultimate goal of applying design principles/patterns is to improve code readability, extensibility, reusability, maintainability, etc.


â‘¡ Open Closed Principle (OCP)

Open to extension, closed to modification, that is, adding a new feature should extend the code base, not modify the existing code.

Here’s a simple example:

public class OcpTest {
    public static void main(String[] args) {
        GraphicEditor editor = new GraphicE-ditor();
        editor.drawShape(new Rectangle());
        editor.drawShape(newCircle()); }}/ / drawing class
class GraphicEditor {
    public void drawShape(Shape shape) {
        if (shape.type == 1) drawRectangle(shape);
        else if(shape.type == 2) drawCircle(shape);
    }
    public void drawRectangle(Shape s) { System.out.println("Draw a rectangle."); }
    public void drawCircle(Shape s) { System.out.println("Draw circles"); }}// Graph class, with only one type attribute representing the category
class Shape { int type;  }
class Rectangle extends Shape { Rectangle() { super.type = 1; }}class Circle extends Shape { Circle() { super.type = 2; }}Copy the code

If you want to add a triangle drawing function, you need to modify the above code:

Class GraphicEditor {public void drawShape(Shape Shape) {if (Shape. Type == 1) drawRectangle(Shape); else if(shape.type == 2) drawCircle(shape); else if(shape.type == 3) drawTriangle(shape); } public void drawRectangle(Shape s) {system.out.println (" rectangle "); } public void drawCircle(Shape s) {system.out.println (" drawCircle "); } public void drawTriangle(Shape s) {system.out.println (" drawTriangle "); } class Shape {int type; } class Rectangle extends Shape { Rectangle() { super.type = 1; } } class Circle extends Shape { Circle() { super.type = 2; } } class Triangle extends Shape { Triangle() { super.type = 3; }} // change â‘ Copy the code

Three changes at once, where the drawing class can be seen as the upstream system (caller) and the drawing class as the downstream system (provider), the vision of the open closed principle:

Extend to provider, close to caller modification (with little or no change)

So this is clearly against the open close principle, so what should be done:

Encapsulate mutable parts, isolate changes, and provide an abstract immutable interface for upstream system calls. When the implementation changes, a new implementation can be extended to replace the old one, based on the same abstract interface, and the code of the upstream system needs little modification.

With that in mind, let’s change the code above to the draw() method, change Shape to an abstract class, define the method, and let subclasses implement it.

Class GraphicEditor {public void drawShape(Shape Shape) {shape.draw(); }} // Shape class Shape {int type; public abstract void draw(); } class Rectangle extends Shape {Rectangle() {super.type = 1; } @override public void draw() {system.out.println (" draw rectangle "); } } class Circle extends Shape { Circle() { super.type = 2; } @override public void draw() {system.out.println (" draw "); } } class Triangle extends Shape { Triangle() { super.type = 3; } @override public void draw() {system.out.println (" draw triangle "); }}Copy the code

Now if you want to add an ellipse, just integrate Shape, rewrite draw(), and leave nothing to the GraphicEditor.

How to “open for extensions, closed for modifications” :

Moment with abstraction, encapsulation and extend consciousness, how think about writing code, this code is what are the possible future demand changes, how to design the code structure, achieve good extension points, to demand changes in the future, without changes to the code structure, minimize code changes, insert the new code and flexibly to the extension point.


â‘¢ LSP, Liskov Substitution Principle

The subclass object can replace the parent object anywhere in the program, and ensure that the original logical behavior and correctness of the program is not damaged.

There is a difference between the substitution principle and polymorphism!!

Polymorphism is a feature of object-oriented programming, a way of code implementation, and substitution is a design principle used to guide the design of subclasses in inheritance relationships. Polymorphic syntax code does not report errors when subclasses replace the parent class, which does not mean that it conforms to the principle of In-type substitution. In addition to subclasses can replace the parent class, the principle cannot change the original program logic and destroy the correctness of the original program.

Some examples of violations of the Li substitution principle:

  • â‘  The subclass violates the function that the parent class declared to implement (for example, the order sorting function of the parent class is in ascending order by amount, and the subclass overrides this function to be in ascending order by date);
  • (2) the subclass violates the convention of the parent class on input, output and exception (such as the parent class of a function input can be any integer, the subclass implementation of the input only allow positive integer);
  • â‘¢ Subclasses violate any special instructions listed in the parent class comment;

Here are some tips for determining if subclass design violates the li substitution rule:

Using superclass unit tests to validate subclass code, if some of the unit tests fail, it is possible that the subclass violates the li substitution principle.


â‘£ Interface Segregation Principle (ISP)

A client should not be forced to rely on an interface it does not need. The client here can be understood as the caller or consumer of the interface, and the corresponding server is the designer or provider of the interface.

The interface here is just a descriptive term, and to take our attention away from the implementation details, it can be understood as three things:

1) A set of API interfaces

For example, it provides a set of user-specific apis for other systems to use, including registration, login, and obtaining user information. Now, the background management system needs to implement a function to delete users. Directly adding this interface to the original user Service can solve this problem, but it also brings security risks.

Any system that uses the user Service can call this interface, which is called by other systems without limitation, possibly causing the user to delete it by mistake.

The best solution is to limit the authentication mode of the interface. At the level of code design, the deleted interface can be put into another Service according to the principle of isolation, and then the Service can only be packaged and provided to the background management system.


2) A single API interface or function

Function design should be single function, do not put multiple different function logic in a function to achieve. For example:

public class Statistics {
    private Long max;
    private Long min;
    private Long average;
    private Long sum;
    / /... Omit getters, setters and other methods
}

public Statistics count(Collection dataSet) {
    Statistics statistics = new Statistics();
    / / calculate Max
    / / min
    / / business
    // Calculate sum, etc
    return statistics;
}
Copy the code

Whether the count() function here qualifies as a single responsibility depends on the scenario, for example, if all statistics in count() are involved in each count, that would be a single responsibility.

If some use only Max and min, and some use only sum and average, then it is necessary to calculate all the statistics each time count() is called, and should be broken up into more fine-grained statistical functions, such as:

public Long max(Collection dataSet) { / /... }
public Long min(Collection dataSet) { / /... }
Copy the code

The difference between the single responsibility principle and the interface isolation principle:

The two are somewhat similar, but the former focuses on the design of modules, classes and interfaces, while the latter focuses on the design of interfaces from different perspectives. It provides a standard to judge whether interfaces have a single responsibility: Indirectly by how the caller uses the interface, if the caller uses only part of the interface or part of the function of the interface, then the design of the interface is not responsible enough.


3) Interface concepts in OOP

For example, three external systems are used in the project: Redis, MySQL and Kafka. Each system corresponds to a class of configuration information:

public class RedisConfig {
    private ConfigSource configSource; // Configuration center (such as ZooKeeper)
    private String address;
    private int timeout;
    private int maxTotal;
    / / omit other configuration: maxWaitMillis, maxIdle minIdle...

    public RedisConfig(ConfigSource configSource) {
        this.configSource = configSource;
    }

    public String getAddress(a) {
        return this.address;
    }
    / /... Omit other get(), init() methods...

    public void update(a) {
      / / from configSource loading configuration to address/timeout/maxTotal...}}public class KafkaConfig { / /... Omit... }
public class MysqlConfig { / /... Omit... }
Copy the code

Then add the requirement: Redis and Kafka configuration information needs hot update, MySQL does not need to abstract an update interface Updater

public interface Updater { void update(a); }

public class RedisConfig implements Updater(a) { void update(a) { / *... Implement */}}public class MysqlConfig implements Updater(a) { void update(a) { / *... Implement */ }}

public class Application {
    ConfigSource configSource = new ZookeeperConfigSource(/* omit the argument */);
    public static final RedisConfig redisConfig = new RedisConfig(configSource); 
    public static final KafkaConfig kafkaConfig = new KakfaConfig(configSource); 
    public static final MySqlConfig mysqlConfig = new MysqlConfig(configSource);
    
    public static void main(String[] args) { 
        ScheduledUpdater redisConfigUpdater = new ScheduledUpdater(redisConfig, 300.300);  
        redisConfigUpdater.run(); 
        ScheduledUpdater kafkaConfigUpdater = new ScheduledUpdater(kafkaConfig, 60.60); kafkaConfigUpdater.run(); }}Copy the code

Then a new requirement is added: MySQL and Redis need monitoring functionality, Kafka does not need to abstract a monitoring interface Viewer

public interface Viewer { 
    String outputInPlainText(a); 
    Map output(a);
}
Copy the code

In the same way, MySqlConfig and RedisConfig implement this interface rewrite method. ScheduledUpdater only relies on the Updater interface instead of being forced to rely on the unneeded Viewer interface, which meets the interface isolation principle.

If you don’t follow this principle and instead use a large, all-inclusive Config interface that each Config inherits, you end up doing a little bit of unproductive work.

MySQL does not need hot updates, but it needs to implement hot update() methods. Kafka does not need monitoring. In addition, a new interface is added to Config, and all implementation classes are changed.


⑤ DIP, Dependence Inversion Principle

High-level modules should not depend on low-level modules, but should depend on each other through abstraction. Abstraction should not depend on concrete implementation details, which depend on abstraction.

The definition is a little hard to understand. The above picture is an example:

The upper layer is the lamp, and the lower layer is the wire in the wall, and the lamp depends on the wire, which means you have to manually solder the lamp to the wire to make the light come on (the upper layer depends on the lower layer).

Pretty retarded, huh? The interaction between them is really the connection, not the lamp connection, the wire connection, but the abstraction of a protocol/constraint/specification → the connection socket.

It doesn’t matter if you’re a lamp, refrigerator, 4 square wire, or 6 square wire (independent of implementation details), but if you want to connect, you have to follow the socket specification (implementation details depend on abstraction).

Significance of using DIP:

  • Effectively control the range of influence of code changes → unified interface, interface unchanged, how to change the external system, the internal system need not change.
  • Make code more readable and maintainable → After code is abstracted uniformly, the same processing is in the same place.

Write an example using the lights and wires above:

public class Lamp {
    void weld(String origin) {
        System.out.println("Weld to"+ origin); }}public class Wire {
    String pull(a) { return "Wire in the Wall."; }}public class ConnectProcessor {
    private Lamp lamp;
    private Wire wire;

    public ConnectProcessor(Lamp lamp, Wire wire) {
        this.lamp = lamp;
        this.wire = wire;
    }

    public void connect(a) {
        lamp.weld(wire.pull());
    }

    // Test case
    public static void main(String[] args) {
        Lamp lp = new Lamp();
        Wire we = new Wire();
        ConnectProcessor processor = new ConnectProcessor(lp, we);
        processor.connect();    // Output: weld wires into the wall}}Copy the code

ConnectProcessor is the high-level component, Lamp and Wire are the low-level components. The code seems simple, but it has two problems. The first one is: The ConnectProcessor has poor reusability, so it needs to write a lot of repetitive code for reusability, introduce abstractions and isolate changes, and define a separate interface of IConnectProcessor. ConnectProcessor implements this interface:

public interface IConnectProcessor {
    void connect(Lamp lamp, Wire wire);
}

public class ConnectProcessor implements IConnectProcessor {
    @Override
    public void connect(Lamp lamp, Wire wire) {
        lamp.weld(wire.pull());
    }

    public static void main(String[] args) {
        Lamp lp = new Lamp();
        Wire we = new Wire();
        ConnectProcessor processor = newConnectProcessor(); processor.connect(lp, we); }}Copy the code

The second problem is that higher-level components depend on lower-level components, and changes to the latter affect the former, so the lower-level components need to be abstracted as well:

public interface ILamp {
    void weld(String origin);
}

public interface IWire {
    String pull(a);
}

public class Lamp implements ILamp {
    @Override
    public void weld(String origin) {
        System.out.println("Weld to"+ origin); }}public class Wire implements IWire {
    @Override
    public String pull(a) {
        return "Wire in the Wall."; }}public interface IConnectProcessor {
    void connect(ILamp lamp, IWire wire);
}

public class ConnectProcessor implements IConnectProcessor {
    @Override
    public void connect(ILamp lamp, IWire wire) {
        lamp.weld(wire.pull());
    }

    public static void main(String[] args) {
        ILamp lp = new Lamp();
        IWire we = new Wire();
        ConnectProcessor processor = newConnectProcessor(); processor.connect(lp, we); }}Copy the code

ConnectProcessor relies on Lamp and Wire (upper layer depends on lower layer), abstracts the rule IConnectProcessor, and then the concrete implementation of the module depends on this rule, this is dependency inversion!!

With dependency inversion in mind, let’s talk about three other nouns we hear frequently to avoid confusion:

1) IOC, Inversion Of Control

A more general design idea, generally used to guide the framework level design, is to give the control of the program execution flow to the framework to complete.

2) Dependency Injection (DI)

Implement IOC’s design pattern by creating dependent objects outside the class and providing objects to the class in different ways (constructors, properties, methods).

3) Dependency Injection Framework (DI Framework)

The framework used to implement automatic dependency injection manages the creation and life cycle of objects, and provides specific implementation of injecting dependencies into classes. There are many ready-made injection frameworks, such as Java Spring, ButterKnife and Dagger2 in Android, and so on.


0x2. KISS principle

Keep It Simple and Stupid. → Code as Simple as possible

It’s not that fewer lines of code makes it easier — there’s also logic complexity, implementation difficulty, code readability, etc. It is not because the code logic is complex that it violates the KISS principle, → complex problems can be solved with complex methods (such as KMP algorithm).

The same code that satisfies the KISS principle in one business scenario may not satisfy it in another.

How to write code that satisfies the KISS principle:

  • Don’t implement code using techniques your colleagues may not understand (e.g., re, overly advanced syntax in a programming language);
  • Don’t duplicate the wheel, but use the existing tool library classes;
  • Don’t over-optimize, over-use fancy tricks (bitwise operations instead of arithmetic, complex conditional statements instead of if-else)

Whether the Code is simple enough is also a subjective judgment, which can be verified indirectly through Code Review.

By the way, the YAGNI principle, You Ain’t Gonna Need It, the core idea is:

Don’t overdesign, don’t do what you don’t need right now! (such as introducing a bunch of dependency libraries that are not currently needed)

!!!!!!!!! This doesn’t mean you don’t need to consider extensibility of your code, but reserve extension points for implementation when needed.


0x3 DRY principle

Don’t Repeat Yourself → Three kinds of code repetition in programming:

â‘  Implement logical repetition

Logical repetition, but functional semantic repetition, does not violate the DRY principle. For example, there are two methods, one for user name validation and one for password validation, and the validation logic is now consistent: nullation → length (4-64) → array or letter composition. Here’s the question:

Duplicate code for validation logic, violating the DRY principle? Isn’t it beautiful to combine the two methods into one?

On the contrary, it violates the principle of single responsibility and interface isolation, and you may have to do it again when you combine the requirements for later product changes, such as changing the username length to (4-20), supporting emoji, 23333.

In addition, there is no violation of the DRY principle, which is semantically different: one authentication username, one authentication password. A better solution to this problem is to abstract the length and character limit logic into two other functions, and pass arguments dynamically.

â‘¡ Functional semantic repetition

For example, to check whether the IP address is valid, there are two different verification logic methods written in the project. The logic is not repeated and the function is repeated, which violates the DRY principle.

In addition, such operation is also in the “pit”, the project for a while to adjust this and that, increased the difficulty of reading, thought there was a deeper consideration, the result is the code design problem. And there is a pit, when the rule to determine whether the IP is legal changes, change one forgot to change another, or do not know there is another, there will be some puzzling bugs.

â‘¢ Code execution is repeated

For example, the logic to verify that the user logged in successfully:

Check whether the user name exists → Check whether the user name and password exist → Check whether the user information exists

Check whether there is a step that can be skipped. I/O operations are time-consuming, so try to reduce such operations.

In addition, if a function is called repeatedly (for example, to verify that email is valid), try refactoring the code to remove duplicate code.

Code Reusability

First distinguish concepts:

  • Code reuse → develop new functions reuse existing code as far as possible;
  • Code reusability – the property or ability of a piece of code that can be reused.
  • DRY principle → Don’t write duplicate code;

How to improve code reuse

  • Reduce code coupling;
  • Meet the single responsibility principle;
  • Modular; (Not limited to modules composed of a group of classes, can also be understood as a single class, function)
  • Separation of business and non-business logic; (The more business-neutral the code, the easier it is to reuse and extract it into common frameworks, libraries, components, etc.)
  • Common code sink; (Layering perspective: the lower the code is more common, should be designed to be reusable enough to prevent the lower code to call the upper code)
  • Inheritance, polymorphism, abstraction, encapsulation;
  • Apply design patterns such as templates.

0x4 LOD (Law Of Demeter)

Before explaining this principle, let’s take a look at the common term high cohesion and low (loose) coupling:

1) high cohesion

Similar functions should be placed in the same class, and unrelated functions should not be placed in the same class. Similar functions are often modified at the same time, in the same class, the changes are more centralized, the code is easy to maintain.

(2) low coupling

The dependencies between classes are simple and clear. Even if two classes have dependencies, code changes in one class will not or rarely result in code changes in the dependent class.

â‘¢ The relationship between high cohesion and low coupling

High cohesion → guiding class design, low coupling → guiding class and inter-class dependency design;

High cohesion contributes to low coupling, which in turn requires high cohesion.

â‘£ The principle of minimum knowledge

Demeter’s Rule, you can’t guess what it means just by looking at its name.

It was designed by Ian Holland in the fall of 1987 at Northeastern University for a project called Demeter.

It is also known by a more flattering name, the principle of least Knowledge, and reads as follows:

Don’t have dependencies between classes that shouldn’t have direct dependencies; Between classes that have dependencies, try to rely on only the necessary interfaces.

It’s a little confusing, but let’s take a classic example to help you understand the simulation of the supermarket shopping process

/ / wallet
public class Wallet {
    private float balance;  // Wallet balance
    
    public Wallet(float money) { this.balance = money; }
    
    // Are methods of getting, setting, increasing, and decreasing balances
    public float getBalance(a) { return balance; }
    public void setBalance(float balance) { this.balance = balance; }
    public void increaseBalance(float deposit) { balance += deposit; }
    public void decreaseBalance(float expend) { balance -= expend; }}/ / the customer class
public class Customer {
    private String name;
    private Wallet wallet;
    
    public Customer(String name, Wallet wallet) {
        this.name = name;
        this.wallet = wallet;
    }
    
    // Set & get the name and wallet in turn
    public String getName(a) { return name; }
    public void setName(String name) { this.name = name; }
    public Wallet getWallet(a) { return wallet; }
    public void setWallet(Wallet wallet) { this.wallet = wallet; }}// The cashier class
public class Cashier {
    public void charge(Customer customer, float payment) {
        System.out.println("You need to pay:" + payment + "Yuan");
        Wallet wallet = customer.getWallet();
        if (wallet.getBalance() > payment) {
            wallet.decreaseBalance(payment);
            System.out.println("The deduction is successful, you still have:" + wallet.getBalance());
        } else {
            System.out.println("Failed deduction, your wallet only has:"+ wallet.getBalance()); }}}// Test case
public class Shopping {
    public static void main(String[] args) {
        Customer customer = new Customer("Jacob".new Wallet(100.0 f));
        Cashier cashier = new Cashier();
        cashier.charge(customer, 66.6 f); }}// Run the following command:
// You need to pay: 66.6 yuan
// You have 33.4 left in your wallet
Copy the code

The output is fine, so the code looks fine, right? But it actually violates Demeter’s law. Consider the process above:

Checkout: the customer gives the wallet to the cashier → cashier checks whether the balance is enough to pay → if it is enough, deduct the inside of the front and then tell you the balance??

How much money in your wallet does it matter to the cashier? Such a design is obviously unreasonable:

The cashier just needs to have received enough money, and the customer just needs to keep his wallet in order to pay for it. Money is decoupled:

public class Customer {
    / /... Add a new method of paying in cash
    public float payCash(float amount) {
        if(wallet ! =null) {
            if(wallet.getBalance() > amount) {
                wallet.decreaseBalance(amount);
                returnamount; }}return 0; }}public class Cashier {
    // Modify this method
    public void charge(Customer customer, float payment) {
        System.out.println("You need to pay:" + payment + "Yuan");
        float customerPay = customer.payCash(payment);
        if(customerPay == payment) {
            System.out.println("Successful deduction, welcome to visit next time ~");
        } else {
            System.out.println("The amount paid is inconsistent with the amount to be paid!"); }}}// Run the following output:
// You need to pay: 66.6 yuan
// Successful deduction, welcome next time ~
Copy the code

A little movement, the use of Demeter’s law, decoupled and improved the reuse of the code, such as customers to wechat pay, pay on behalf of the cashier will not accept the behavior.

Demeter’s law is not only applied to class design, but also to architectural layering:

Each layer of modules can only call modules in its own layer. To skip a layer and directly call modules in another layer is a violation of the principle of hierarchical architecture.

Demeter’s law is not perfect, of course:

It is easy to introduce too many intermediate classes and methods that are too small. The efficiency of passing messages between different modules may decrease (need to span multiple mid-tier modules);

⑤ Extension: AOP, Aspect Oriented Programming

To put it simply, it is a technique to dynamically add functionality to a program without modifying the functionality of the existing program code.

Demeter’s law reduces code coupling at program design time (statically), whereas AOP reduces code coupling at program run time (dynamically).

The difference between OOP and AOP

  • OOP → emphasizes the intrinsic nature of objects and is more suitable for business functions, such as goods, orders, and memberships.
  • AOP → for **Unified behavior and action**, such as logging and performance statistics, focuses on the behavior of the system itself without affecting the implementation and evolution of functional services.

summary

The content is a little too much, sort it out, convenient to remember:

  • Single Responsibility principle (SRP) → A class/module performs only one responsibility;
  • Open closed principle (OCP) → Open to extension (provider), close to modify (caller) → encapsulate the variable part, provide abstract immutable interface for the caller to call;
  • In substitution principle (LSP) → The subclass object can replace the parent class object, while ensuring that the logical behavior of the program is unchanged and the correctness is not damaged;
  • Interface Isolation Principle (ISP) → Do not provide the caller with some interface or method it does not need;
  • The principle of Dependency inversion (DIP) → high-level modules should not directly depend on low-level modules, but abstract a protocol between modules, and rely on each other by implementing this protocol;
  • KISS principle → Keep code as simple as possible;
  • YAGNI principle → don’t over design;
  • DRY principle → Don’t repeat yourself, distinguish between logical repetition, semantic repetition, and code execution repetition!
  • Demeter’s Law (LOD) → Classes that should not have dependencies do not rely on them. Classes that have dependencies should only rely on necessary interfaces as much as possible.

Theory should be applied in practical development, and recently these principles have been applied to project development, which has been a revelation for a long time.

Not just to get things done, but to get things done, not just to throw code around, but to weigh them

Do not know how to express, anyway, high cohesion, low coupling in mind is right ~


References:

  • “Fun learning design patterns” 09 | minimum principle: how to implement the “minimum knowledge” code?