object-oriented software design

Recently, I encountered some problems in the project development process, and found that in each wave of iterative development process, it is often necessary to modify the previous code. Although such a situation is normal, new requirements will inevitably bring new functions and new designs. Causes the previous code to be affected. I remember seeing a joke:

"You don't need to use a gun to kill a programmer, just change the requirements three times."

In fact, requirements design is one aspect. In addition, as designers and developers, we sometimes need to reflect on whether the code design is reasonable, why it is so difficult to expand new functions on the original code, and why our code is so unstable. Move the whole body at once?
  I think to be a programmer, at least not a stupid person. If you want to complete a function, you can always find a way to achieve it (otherwise you would have been fired~), but the way to achieve it is good or bad, but I Think that ideas can be guided, software development is not just beginning, it has existed for a while, we can absorb some experience and lessons from our predecessors to improve ourselves, such as GOF's "Design Patterns: The Foundation of Reusable Object-Oriented Software", help us Summarized the solutions to many problems. During this time, I also spent some time learning some ideas of object-oriented design, and also talked about some of my understanding.
  When it comes to design patterns, I think many people have read some books in this area, but I don’t know if they will have the same confusion as me: I understand it when I read it, but I can’t integrate it in the actual development, and I will slowly learn about it later. forgotten. Embarrassing, maybe we only see the surface of a certain pattern, but some "truths" hidden behind the pattern have not been discovered. What problem does this pattern solve? In fact, behind the pattern design is to follow a certain design principle.

"More important than design patterns are design principles"

Everyone knows the concept of object-oriented design. Its design goal is to hope that the software system can do the following:

  • Extensible: new features can be easily added to existing systems without affecting the original
  • Modifiable: When modifying a certain part of the code, it will not affect other unrelated parts
  • Substitutable: When a certain part of the code in the system is replaced with other classes with the same interface, it will not affect the existing system

These few can be used to detect whether our software system is designed reasonably, and how to design a software system that is easy to maintain and expand is a design principle that can be followed. Robert C. Martin proposed five basic object- oriented design Principles (SOLID):

  • S - Single Responsibility Principle
  • O - Open Closed Principle
  • L - Liskov Substitution Principle
  • I-Interface Segregation Principle
  • D - Dependency Inversion Principle

We should keep these principles in mind when doing object-oriented design, which can make you a better design developer --- at least your code will not be so bad, let's briefly understand these principles.

Single Responsibility Principle: Single Responsibility Principle

A class has one and only one responsibility, and only one reason for its change.

To put it simply, a class can only do one thing well, and don’t care about things that have nothing to do with itself. Dogs and mice meddle in their own business. The core is decoupling and high cohesion. This principle looks very simple. Even if we don’t know this principle when writing code, we will move closer to this direction and write classes with relatively single functions. However, this principle is easy to violate, because for some reason, the original function is single The class needs to be refined into smaller-grained responsibility 1 and responsibility 2, so in each iteration process, it may be necessary to reorganize the code written before refactoring, and encapsulate different responsibilities into different classes or modules.
Take a chestnut:

 

@interface DataTransfer : NSObject
-(void)upload:(NSData *)data; //上传数据
-(void)download(NSString*)url;  //根据URL下载东西
@end

DataTransfer includes upload and download functions. After careful consideration, it can be found that this is equivalent to implementing two functions, one is responsible for the related logic of uploading, and the other is responsible for the logic of downloading. These two functions are relatively opposite. When one function changes, For example, we used AFNetworking before, but now we want to switch to other third parties or nsurlconnection to upload and download:

  • Changes in the upload method lead to changes in DataTransfer
  • Changes in the download method lead to changes in DataTransfer

This violates the principle of single responsibility, so different functions need to be disassembled into two different classes to be responsible for their respective responsibilities. However, the granularity of this disassembly may vary depending on the person. Sometimes it does not need to be dismantled too carefully. Don’t Became design for design.

 

single responsibility

 

  In our projects, we often see a lot of code that violates this principle, and the violation is more obvious. Many classes are a super collection of rich functions, and the whole class becomes bloated and difficult to understand. At this time, we need to consciously refactor .

Open Closed Principle: Open Closed Principle

The definition of the open-closed principle is that a software entity such as a class, module, and function should be open to extension, but closed to modification. Specifically, you should implement changes through extensions, not by modifying the original code. This principle is the most basic principle of object-oriented design.
  I said before that whenever the requirements need to be changed in the project, it is often necessary to make great changes to the code, largely because our understanding of this principle is not thorough enough.
  The key to the principle of opening and closing lies in abstraction. We need to abstract out those things that will not change or basically remain unchanged. These things are relatively stable, which is where the modification is closed (this does not mean that it cannot be modified), and For those parts that are easy to change, we also encapsulate them, but this part can be dynamically modified, which is where the extension is developed. For example, the strategy mode and template mode in the design mode are implementing this principle (now the mode should be Have a more perceptual understanding~).

For example: we need to save objects to the database, there is a save method similar to save(), this part should remain unchanged, the interface is relatively stable, but the implementation of specific save may be different, we may now save In the Sqlite database, if we want to save it in a self-implemented database in the future, we only need to implement an extension class with the same interface and add it in. This is open to extensions and will not have any impact on the previous code. , the function of saving to the new database can be realized, which ensures the stability of the system.

 

Open and close principle

 

The guiding ideology to realize the principle of opening and closing is:

  • Abstract a relatively stable interface, this part should not be changed or rarely changed
  • package change

However, in the process of software development, it may be difficult to completely follow the open-close principle from the beginning. More often, it is to improve in the process of continuous iterative refactoring, and to design within the foreseeable range of changes.

Liskov Substitution Principle: Liskov Substitution Principle

Definition of this principle: All references to the base class must be able to transparently use objects of its subclasses. To put it simply, if all places where the base class code is used can still run normally when replaced with a subclass object, then this principle is satisfied, otherwise there is a problem with the inheritance relationship, and the inheritance relationship between the two should be abolished. This principle can be used Determine whether our object inheritance relationship is reasonable.
For example, there is a whale class, we let the whale inherit from the fish, and then the fish has a breathing function:

 

whales inherit from fish

Then while in the water, the fish can breathe:

 

if(isInwater){
    //在水中了,开始呼吸
    fish.breath();
}

When we replace the sub-object of the whale with the original base class fish object, the whale starts to breathe in the water, and then the problem arises. The whale is a mammal, and it cannot breathe in the water. It will be GG if it is always in the water. Smecta, so this violates the principle, we can judge that it is unreasonable for whales to inherit from fish, and it needs to be redesigned.
  Usually when designing, we will give priority to composition rather than inheritance, because although inheritance reduces code and improves code reusability, the parent class and subclass will have a strong coupling, breaking the encapsulation.

Interface Segregation Principle: Interface Segregation Principle

Definition of this principle: Users cannot be forced to rely on interfaces that they do not use. Simply put, what kind of interface is provided to the client, and other redundant interfaces should not be provided, so as not to make the interface bloated, otherwise when an unused method of the object is changed, the object will also will be affected. The design of the interface should follow the principle of the smallest interface. In fact, this is also a manifestation of high cohesion. In other words, it is better to use multiple interfaces with a single function and high cohesion than to use a huge interface.
  To give a simple example: For example, we have a bicycle interface, this interface contains many methods, including GPS positioning, and the method of shifting gears

 

Does not meet ISP principles

 

 Then we found that even ordinary bicycles need to implement GPS positioning and shifting functions, which obviously violates the principle of interface isolation. Following the principle of interface minimization, we redesign:

 

Meet ISP principles

 

  In this way, the function of each interface is relatively single, and it is better to use multiple specialized interfaces than to use a general interface. If our mountain bike does not have the function of GPS positioning, we do not need to inherit and implement the corresponding interface. In iOS There are many such examples in development. For example, the proxy of UITalbleView has two different interfaces. UITableViewDataSource is responsible for the content that needs to be displayed, and UITableViewDelegate is responsible for the custom display of some views. Then we will inherit multiple interfaces, which satisfies the ISP in principle.

 

@interface ViewController () <UITableViewDataSource,UITableViewDelegate,OtherProtocol>

Dependency Inversion Principle: Dependence Inversion Principle

Definition of this principle: High-level modules should not depend on low-level modules, both should depend on their abstractions; abstractions should not depend on details; details should depend on abstractions. In fact, this is what we often say "programming to the interface". The interface here is abstraction, and we should rely on the interface instead of relying on the specific implementation to program.
  If you develop a new database system AWEDatabase on the basis of Sqlite database, then Sqlite is equivalent to the bottom module, and your AWEDatabase belongs to the high-level module; and from the perspective of AWEDatabase development users, its business layer is equivalent to A high-level module, and AWEDatabase becomes a low-level module, so the level of the module should be viewed from the current perspective of the developer, but the DIP principle is suitable and needs to be observed from different perspectives. If our high-level modules directly depend on the low-level modules, the consequence is that every time the low-level modules are changed, the high-level modules will be affected, and the entire system will become unstable, which also violates the open-closed principle.
  Usually we solve this problem by introducing an intermediate layer. This intermediate layer is equivalent to an abstract interface layer. Both high-level modules and low-level modules rely on this intermediate layer to interact. In this way, as long as the intermediate abstraction layer remains unchanged, the underlying modules change. It will not affect the high-level modules, which satisfies the principle of openness and closure; and if the high-level modules and the bottom-level modules are in the development stage at the same time, with the intermediate abstraction layer, each module can be developed for the interface of this abstraction layer at the same time, and the high-level The module does not need to wait until the underlying module is developed before continuing.
  For example, in our project, there are functions related to IM. Now this IM module is implemented by the XMPP protocol. The client uses this module to realize the sending and receiving of messages. However, if we want to switch to other protocols later, such as MQTT, etc., for Interface programming allows us to easily implement module replacement:

 

Programming against the interface

 

@protocol MessageDelegate <NSObject>
@required
-(void)goOnline;
-(void)sendMessage:(NSString*)content;
@end

//xmpp实现
@interface XMPPMessageCenter <MessageDelegate>
@end

//MQTT实现
@interface MQTTMessageCenter <MessageDelegate>
@end

//业务层
@interface BussinessLayer
//使用遵循MessageDelegate协议的对象,针对接口编程,以后替换也很方便
@property(nonatomic,strong)id<MessageDelegate> messageCenter;
@end

When we are doing object-oriented design, we should fully consider the above principles. The design may not be perfect at the beginning, but it can be continuously improved in the process of refactoring. But in fact, many people skip the design process, get a module and write the code directly, let alone think about the design. There are many such examples in the project. Of course, there may not be much design for simple modules, but if the modules are relatively complex, you can design and think about it before writing code by hand. Developing this habit will definitely improve the readability, stability and scalability of the code. The code is helpful.

The most critical software development tool is a mind trained in the principles of good design.

Guess you like

Origin blog.csdn.net/gp18391818575/article/details/112696680