Introduce design principles and design patterns

The role of design patterns

  • Answer questions about design patterns in the interview;
  • Say goodbye to writing bad code that people make fun of;
  • Improve the design and development ability of complex code;
  • Let read the source code, learning framework multiplier;
  • Prepare for your career development.

High quality code

  • Maintainability: The ability to quickly modify or add code without breaking the original code design or introducing new bugs.
  • Readability: Good programmers write code that people can understand. Whether the code conforms to the code specification, whether the name is meaningful, whether the comments are detailed, whether the function length is appropriate, whether the module division is clear, whether it conforms to high cohesion and low coupling, etc.
  • Extensibility: There are extension points reserved for the code, so you can plug new feature code directly into the extension point, without having to change a lot of original code to add a feature.
  • Flexibility: Code that is easily extensible, reusable, or easy to use.
  • Simplicity: Keep your code as simple as possible.
  • Reusability: minimize the writing of duplicate code and reuse existing code.
  • Testability: Unit testable.
  • Overview of methods for writing high-quality code: object-oriented design ideas, design principles, design patterns, coding specifications, refactoring techniques.

An overview of ways to write quality code

  • Object orientation, design principles, design patterns, programming specifications, and code refactoring are all methodologies for maintaining or improving code quality, essentially in the service of writing high-quality code.

  • object-oriented

    • Object – oriented four features: encapsulation, abstraction, inheritance, polymorphism
    • The difference and relation between object-oriented programming and procedural programming
    • Object-oriented analysis, object-oriented design, object-oriented
    • The differences between programming interfaces and abstract classes and their application scenarios
    • Design ideas based on interfaces rather than implementation programming
    • The design idea of more combination and less inheritance
    • Process – oriented anemia model and object – oriented congestion model
  • Design principles

    • SOLID Principle -SRP single responsibility principle
    • SOLID principle -OCP open and close principle
    • SOLID principle -LSP replacement principle
    • SOLID principle -ISP interface isolation principle
    • SOLID principle -DIP dependence inversion principle
    • DRY principle, KISS principle, YAGNI Principle, LOD principle
  • Design patterns

    • Creation: singleton pattern, factory pattern (factory method and abstract factory), Builder pattern. Less commonly used are: prototype patterns.
    • Structural: Agent mode, bridge mode, decorator mode, adapter mode. Not commonly used are: facade mode, combination mode, enjoy yuan mode.
    • Behavior: Observer mode, template mode, policy mode, responsibility chain mode, iterator mode, state mode. Less commonly used are: visitor mode, memo mode, command mode, interpreter mode, mediation mode.
  • Programming specification

    • How to name variables, classes, functions, how to write code comments, functions should not be too long, too many parameters, and so on.
  • Code refactoring

    • The purpose of reconstruction (why), object (what), time (when), method (how);
    • Technical means of ensuring error-free refactoring: unit testing and testability of code;
    • There are two types of refactoring at different scales: large refactoring (large-scale high-level) and small refactoring (small-scale low-level).

Object oriented definition

  • Definition: Object-oriented programming language is to support the syntax mechanism of class or object, and has a ready-made syntax mechanism, can easily realize the four characteristics of object-oriented programming (encapsulation, abstraction, inheritance, polymorphism) programming language.

  • Object-oriented analysis and design: To do requirements analysis and design around objects or classes. The final output of the two stages of analysis and design is the design of classes, including which classes the program is disassembled into, which attribute methods each class has, and how classes interact with each other.

  • UML:

    • Class diagrams, use case diagrams, sequence diagrams, activity diagrams, state diagrams, component diagrams,
    • Class relationships: generalization, implementation, association, aggregation, composition, dependency
  • encapsulation

    • Encapsulation is also called information hiding or data access protection. By exposing a limited access interface, a class authorizes outsiders to access internal information or data only through the means (or functions) provided by the class.
    • Restrict arbitrary method properties to improve readability and maintainability.
    • By making classes easier to use, callers don’t need to know as much about the business details behind them.
  • abstract

    • Abstraction is about hiding the concrete implementation of a method so that the caller only needs to know what the method provides, not how it is implemented.
    • Abstraction, as a design approach that focuses on function points rather than implementation, helps our brains filter out a lot of unnecessary information.
    • Code design, play a very important role in guidance. Many design principles embody this idea of abstraction, such as programming based on interfaces rather than implementations, the open and closed principle (open to extensions, closed to modifications), and code decoupling (reducing code coupling).
  • inheritance

    • Inheritance is used to represent is-A relationships between classes.

    • Code reuse

      • A subclass can reuse code from its parent class, preventing code from being written more than once.
      • Inheritance reflects two class relationships.
  • polymorphism

    • Polymorphic: A subclass can replace its parent class and call its method implementation during actual code execution.
    • Implementation: inheritance and method rewrite, using the interface class to achieve polymorphic characteristics.
    • Duck-typing is very flexible in the way it implements polymorphism. There is no relationship between the two classes, neither inheritance nor interface and implementation, but as long as they are both defined.
    • Polymorphism improves the extensibility and reusability of code.
    • Polymorphism is also the code implementation basis for many design patterns, design principles, and programming techniques.

Process oriented and object oriented

  • Object-oriented programming takes class as the basic unit of code organization and supports inheritance, polymorphism and encapsulation

  • Process oriented programming takes process (or method) as the basic unit of organizing code, and separates data and method.

  • Richer features of object-oriented programming (encapsulation, abstraction, inheritance, polymorphism). The code written by using these features is easier to expand, reuse and maintain.

  • Note that object orientation writes procedural code

    • Abusing getters and setters
    • Abusing global variables and methods: refining function classes
    • Define classes that separate data from methods
    • Reason: Streamline thinking
    • Ultimately, the goal is to write high-quality code that is easy to maintain, easy to read, easy to reuse, and easy to extend.

Interfaces and abstract classes

  • Abstract classes are not allowed to be instantiated, only inherited. It can contain properties and methods. Methods may or may not contain code implementations. Methods that do not contain code implementations are called abstract methods. Subclasses inherit from abstract classes and must implement all of the abstract methods in the abstract class.
  • Interfaces cannot contain properties, only methods can be declared, and methods cannot contain code implementations. When a class implements an interface, it must implement all methods declared in the interface.
  • Abstract class is the abstraction of member variables and methods. It is an IS-A relationship to solve the problem of code reuse.
  • An interface is just an abstraction of a method. It is a HAS-A relationship, indicating that it has a certain set of behavior characteristics. It is to solve the decoupling problem, isolate the interface from the concrete implementation, and improve the extensibility of code.
  • The judgment relation chooses abstract class or interface class.

Programming based on interfaces rather than implementations

  • Programming based on abstraction rather than implementation: when doing software development, there must be an awareness of abstraction, encapsulation and interface. The more abstract, the more top-level, the more detached from a specific implementation of the design, the more flexibility, extensibility, maintainability of code.
  • When defining interfaces, on the one hand, the names should be generic and not contain implementation-specific words. On the other hand, implementation-specific methods should not be defined in the interface.
  • Not only can guide very detailed programming development, but also can guide more upper architecture design, system design.

Object-oriented analysis and design case summary

  • For the development of non-business systems such as frameworks, class libraries and components, one of the biggest difficulties is that requirements are generally abstract and vague. You need to dig them out by yourself, make reasonable trade-offs, trade-offs and assumptions, and visualize the abstract problems so as to produce a clear and tangible definition of requirements.
  • The process of requirement analysis is actually a continuous iterative optimization process.
  • Divide responsibilities to identify which classes there are
  • Define a class and its properties and methods
  • Define the interaction between classes: more close to the programming perspective, the relationship between classes has been adjusted, retaining four relationships: generalization, implementation, composition, dependency (return parameters, parameters, local variables).
  • Assemble classes and provide entry to execution

Design principles

Single responsibility principle

  • Definition: A class is responsible for only one responsibility or function. Don’t design large, comprehensive classes. Design small, single-purpose classes. The principle of single responsibility is to achieve high cohesion and low coupling of code, and improve code reusability, readability, and maintainability.

  • Some of the lateral indicators are more instructive and enforceable

    • Too many lines of code, functions, or attributes in a class;
    • A class depends on too many other classes, or depends on too many other classes;
    • Too many private methods;
    • It can be difficult to name a class properly;
    • A large number of methods in a class operate on a few properties of the class.
  • Improve class cohesion by avoiding designing large, all-in-one classes and coupling unrelated functions together. At the same time, the class responsibility is single, the class depends on and depends on other classes will be less, reduce the code coupling, in order to achieve high cohesion, low coupling code.

  • If you break it down too much, it actually does the opposite, reducing cohesion and the maintainability of your code.

The open closed principle

  • Definition: Closed for extension development and modification
  • Adding new functionality should be done by extending the existing code (adding modules, classes, methods, properties, etc.) rather than modifying the existing code (modifying modules, classes, methods, properties, etc.). There are two things to note about this definition.
  • Always have extended consciousness, abstract consciousness, encapsulation consciousness.
  • In writing the code, we should spend more time to think about, what this code possible future demand changes, how to design the code structure, keep good extension points in advance, 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.

Omega substitution principle

  • A principle for how subclasses should be designed in inheritance relationships. The most important thing to understand is “design by contract”. The superclass defines the “conventions” (or protocols) of a function, and the subclass can change the internal implementation logic of the function, but not the original “conventions” of the function. Conventions include: function declaration to implement the function; Conventions for inputs, outputs, and exceptions; Even any special instructions listed in the notes.
  • The difference between the substitution principle and polymorphism: polymorphism is a feature of object-oriented programming and a syntax of object-oriented programming languages. It’s an idea of code implementation. The interior substitution is a design principle used to guide how to design the subclass in the inheritance relationship. The design of the subclass should ensure that when replacing the parent class, the logic of the original program will not be changed and the correctness of the original program will not be damaged.

Interface Isolation Principle

  • To understand “interface” as a single API interface or function, where some callers need only some of the functions in the function, we need to break the function into more fine-grained functions so that the caller only depends on the fine-grained function it needs.
  • The interface isolation principle differs from the single responsibility principle: The single responsibility principle applies to the design of modules, classes, and interfaces. Compared with the single responsibility principle, the interface isolation principle focuses more on interface design on the one hand, and it also has a different perspective. The interface isolation principle provides a criterion for determining whether an interface has a single responsibility: indirectly by how callers use the interface. If the caller uses only part of the interface or part of the function of the interface, the design of the interface is not responsible enough.

Inversion of control, dependency inversion, dependency injection

  • Inversion of control: generally used to guide frame-level design. “Control” refers to the control over the flow of program execution, while “inversion” refers to the control of the entire program execution by the programmer himself before using the framework. After using the framework, the execution flow of the whole program is controlled by the framework. Control of the process is “flipped” from the programmer to the framework.
  • Dependency injection: Instead of creating dependent class objects inside the class using new, the dependent class objects are created outside the class and then passed (or injected) to the class for use through constructors, function parameters, etc.
  • Dependency injection framework: An extension point that allows the framework to automatically create objects, manage the life cycle of objects, and inject dependencies by simply configuring all the required classes and their dependencies between classes.
  • Dependency inversion principle: mainly used to guide framework level design. High-level modules do not depend on low-level modules; they all depend on the same abstraction. Abstraction does not depend on concrete implementation details; concrete implementation details depend on abstraction.

KISS principle and YAGNI principle

  • KISS, keep it simple: An important way to keep your code readable and maintainable. The “simplicity” of the KISS principle is not measured in lines of code. Fewer lines of code does not necessarily mean simpler code; we need to consider logic complexity, implementation difficulty, code readability, and so on. Moreover, complex solutions to complex problems do not violate the KISS principle.

  • KISS principle

    • Don’t implement code using techniques your colleagues may not understand;
    • Don’t reinvent the wheel, be good at using existing toollibraries;
    • Don’t over-optimize.
  • The YAGNI Principle: Do it or not “question (don’t do what you don’t need right now).

DRY principle

  • Definition: Avoid code duplication

  • Classification: logical repetition, functional semantic repetition, code execution repetition. Code that implements logical duplication, but not functional semantics, does not violate the DRY principle. The DRY principle is also violated if you implement code that has the same logic but the same functional semantics. In addition, repeating code execution is a DRY violation.

  • Code reusability:

    • Reduce code coupling
    • Meet the single responsibility principle
    • modular
    • Separation of business and non-business logic
    • Common code sink
    • Inheritance, polymorphism, abstraction, encapsulation
    • Apply design patterns such as templates

Demeter’s rule (LOD)

  • High cohesion and loose coupling: “High cohesion and loose coupling” is a very important design concept, which can effectively improve the readability and maintainability of code and reduce the scope of code changes caused by functional changes. “High cohesion” is used to guide the design of classes themselves, and “loose coupling” is used to guide the design of dependencies between classes. High cohesion means that 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, so the changes are more centralized. Loose coupling means that the dependencies between classes are simple and clear in the code. Even if two classes have dependencies, code changes in one class will not or rarely result in code changes in the dependent class.
  • Demeter’s law: No dependencies between classes that should not have direct dependencies; Try to rely on only the necessary interfaces between classes that have dependencies. Demeter’s rule wants to reduce coupling between classes and make them as independent as possible. Each class should know less about the rest of the system. Once a change occurs, there are fewer classes that need to know about it.

Case summary of design principles

  • Refine the business process through wireframes and user use cases, mining some of the more detailed, hard-to-think function points.
  • The essence of object-oriented design is to put the right code in the right classes. Rational code division can achieve high cohesion, low coupling code, simple and clear interaction between classes, the overall structure of the code at a glance. Analogous to object-oriented design, system design is really about putting the right functions into the right modules. Reasonably divided modules can also achieve high cohesion, low coupling and clean architecture at the module level. In object-oriented design, after the class is designed, we need to design the interaction between the classes. Analogously to system design, once the responsibility of the system is divided, the next step is to design the interaction between the systems.
  • Implementation summary

Specification and refactoring

Methods of code refactoring

  • The purpose of refactoring: For the project, refactoring keeps code quality under control and not beyond redemption. For individuals, and it is a very fulfilling thing. It is the training ground where we learn classical design ideas, principles, patterns, programming specifications and other theoretical knowledge.

  • Objects of reconstruction: According to the scale of reconstruction, we can roughly divide the reconstruction into large-scale high-level reconstruction and small-scale low-level reconstruction.

    • Large-scale high-level refactoring includes layering, modularizing, decoupling, teasing out the interactions between classes, abstracting and reusing components, and so on. This part of the work is more abstract, more top-level design ideas, principles, patterns.
    • Small-scale low-level refactorings include normal naming, annotation, correction of excessive function parameters, elimination of oversized classes, extraction of duplicate code and other programming details, mainly for class and function level refactorings. Small-scale low-level refactoring relies more on theoretical knowledge of coding specifications.
  • Time to refactor: It is important to establish a sense of continuous refactoring as an essential part of daily development, rather than waiting until the code has a major problem.

  • Refactoring methods

    • Large-scale, high-level refactoring is difficult and requires organization, planning, and step-by-step sprinting to keep the code in a working state.
    • Small, low-level refactorings can be done anywhere, whenever you want and have the time, because they have little impact and take less time to change.

Unit tests guarantee error-free refactoring

  • Unit test definition: Unit tests are code-level tests written by developers to test the logical correctness of their code. Unit testing, as the name suggests, tests a “unit,” as opposed to integration testing, which is typically a class or function rather than a module or system.

  • What unit tests do:

    • The process of writing unit tests is itself a process of Code Review and refactoring, which can effectively find bugs in the Code and design problems in the Code.
    • Unit testing is also a great complement to integration testing, a quick way to get familiar with the code, and an implementable improvement to TDD.
  • Write unit test methods

    • Writing unit tests is about designing test cases against code to cover input, exception, and boundary conditions and translating them into code. There are testing frameworks available to simplify writing unit tests.

    • Correct cognitive unit tests

      • Writing unit tests is tedious, but not time-consuming;
      • We can play down the quality of our unit test code a bit;
      • Coverage as the only measure of unit test quality is not reasonable;
      • Unit tests do not rely on the implementation logic of the code under test;
      • Unit testing frameworks cannot be tested, mostly because the code is not testable.
  • Why unit tests are difficult to execute

    • On the one hand, writing unit tests itself is tedious and technically challenging, and many programmers are reluctant to write them.
    • On the other hand, domestic RESEARCH and development tends to be “fast, rough and fierce”, which leads to the execution of unit tests being anticlimactic because of the tight development schedule. In the end, the key problem is that the team has not established a correct understanding of unit testing and feels that it is dispensable and difficult to perform well by pushing alone.

Good testability code

  • Testability of code: How easy it is to write unit tests against your code. If a piece of code is difficult to unit test, or if unit tests are laborious to write and rely on advanced features in the unit testing framework, it often means that the code is not well designed or testable.

  • The most effective way to write testable code: Dependency injection is the most effective way to write testable code. Dependency injection allows us to mock out external services when writing unit tests, which is one of the most technically challenging aspects of writing unit tests.

  • Common code that doesn’t test well:

    • The code contains pending action logic
    • Misuse of mutable global variables
    • Abusing static methods
    • Use complex inheritance relationships
    • Highly coupled code

How do I decouple code

  • Importance: Decoupling ensures loose coupling and high cohesion of code, and is an effective way to control code complexity.

  • Encapsulation and abstraction: It can effectively hide implementation complexity, isolate implementation variability, and provide a stable and easy-to-use abstract interface to dependent modules.

    • By encapsulating it as an abstract open() function, we can effectively control the spread of code complexity, encapsulating complexity in local code.
    • When we change the underlying implementation of a function, we do not need to change the underlying code that depends on it.
  • The middle layer:

    • Introducing an intermediate layer simplifies dependencies between modules or classes.

    • When refactoring, introducing an intermediate layer can serve as a transition, allowing development and refactoring to proceed simultaneously without interfering with each other.

    • Reframe the practice of sprinting

      • Phase 1: Introduce an intermediate layer that wraps around old interfaces and provides new interface definitions.
      • Phase 2: Newly developed code relies on new interfaces provided by the middle tier.
      • Stage 3: Change code that relies on the old interface to call the new one.
      • Phase 4: After ensuring that all code calls the new interface, remove the old interface.
  • modular

    • By dividing the system into separate modules and having different people responsible for different modules, managers can coordinate the modules to make the system work effectively, even without knowing all the details.
    • Focusing on software development, different modules communicate with each other through apis, with little coupling between each module, and each small team focuses on a single, highly cohesive module to develop, and eventually assemble the modules like building blocks to build a super complex system.
    • Focus on the code level. Reasonable module division can effectively decouple the code and improve the readability and maintainability of the code. Developing each module as a separate lib, providing only interfaces to other modules that encapsulate the internal implementation details, reduces coupling between different modules.
  • Other design ideas and principles

    • “High cohesion, loose coupling” is a very important design concept, which can effectively improve the readability and maintainability of code, and reduce the scope of code changes caused by functional changes. The actual

    • The design principle is to achieve “high cohesion, loose coupling” of the code.

      • Single responsibility principle
      • Programming based on interfaces rather than implementations
      • Dependency injection
      • Use more composition and less inheritance
      • Demeter’s rule

20 programming specifications

  • named

    • The key to naming is to be accurate. We can choose different lengths appropriately for naming different scopes. For variables with small scope (such as temporary variables), shorter names are appropriate.
    • Some familiar abbreviations can also be used. We can use class information to simplify the naming of attributes and functions, and use function information to simplify the naming of function parameters. Make your names readable and searchable. Do not use strange, difficult to pronounce English words after the name
    • Use names that conform to the project’s general conventions, and don’t use names that are counterintuitive.
    • An interface can be named in two ways: One is to prefix the interface with “I”. The other is to suffix “Impl” in the implementation class of the interface. There are also two ways to name Abstract classes. One is with the prefix “Abstract”, and the other is without the prefix. Both naming methods are fine, but the key is to be consistent throughout the project.
  • annotation

    • The purpose of comments is to make code easier to read. As long as the content meets this requirement, you can write it in comments.
    • The content of comments mainly includes three aspects: what to do, why, and how to do it. For complex classes and interfaces, we may also need to specify “how to use”.
    • Classes and functions must be commented, and should be written as comprehensive and detailed as possible, while the internal comments of functions should be relatively few, generally rely on good naming, refining functions, explanatory variables, and summary comments to improve code readability.
  • Function, class size is appropriate

    • The number of lines of code in a function should not exceed the size of a screen, such as 50 lines. Class size limits are more difficult to determine
  • How long a line of code should be

    • It is best not to exceed the width of the IDE display. Of course, the limit should not be too small, because too small will result in many longer statements being broken into two lines, and will affect the cleanliness of the code, which is not easy to read.
  • Use blank lines to split cell blocks

    • For long functions, use blank lines to separate code blocks for clarity.
    • Inside a class, blank lines can be added between member variables and functions, between static member variables and normal member variables, between functions, and even between member variables to make the boundaries between different modules’ code clearer.
  • Four indent or two indent

    • Two-space indentation is recommended to save space, especially if the code is deeply nested. In addition, it’s worth emphasizing that whether you use two or four indents, never use the TAB key
  • Whether braces should start on another line

    • Comparisons recommend putting braces on the same line as the previous statement to save lines of code. But putting the braces on a separate line also has the advantage of being vertically aligned, making it easier to see which code belongs to which code block.
  • The order in which the members of a class are arranged

    • In the Google Java programming specification, dependent classes are arranged alphabetically from smallest to largest.
    • A class writes member variables first and then functions. Between member variables or functions, write static member variables or functions first, then ordinary variables or functions, and in order of scope size.
  • Coding skills

    • Distill complex logic into functions and classes.
    • Excessive arguments can be handled by splitting them into functions or encapsulating them as objects.
    • Do not use arguments in functions to control code execution logic.
    • Function design should have a single responsibility.
    • Remove excessive levels of nesting by removing redundant if or else statements, exiting nesting early using the continue, break, and return keywords, adjusting the execution order to reduce nesting, and abstracting some of the nested logic into functions.
    • Replace magic numbers with literal constants.
    • Use explanatory variables to explain complex expressions to improve code readability.
  • Uniform coding specification

    • Projects, teams, and even companies should develop uniform coding practices and follow them through Code Review, which can have an immediate effect on improving Code quality.