What is dependency injection and what is inversion of control, and what is the difference? What is high cohesion and low coupling and how do you achieve it? What is the concept of the open close principle? What is the philosophy behind the various design patterns… If you can answer the above questions, you can stop reading
I recently in the teacher’s king geek time on learning the beauty of the design patterns, this course in a formal interpretation of the various design patterns (the singleton pattern, factory pattern, etc.) before from an object-oriented design principles, programming, and the standard code refactoring, etc, introduces some important concepts and the basic coding knowledge, so that the reader really know much about it. This paper will make a phased summary of the most basic concept of the course — object orientation, so as to better understand the concepts and principles behind various design patterns.
object-oriented
What exactly are we talking about when we talk about “object orientation”? Immediate conclusion: We usually talk about object-oriented programming and object-oriented programming languages. First, a brief concept introduction:
- Object-oriented programming is a programming paradigm or style. It takes classes or objects as the basic unit of code organization, and has four characteristics: encapsulation, abstraction, inheritance and polymorphism.
- Object-oriented programming language is a programming language that supports the syntax mechanism of classes and objects, and has a ready-made syntax mechanism, which can easily implement object-oriented programming (encapsulation, abstraction, inheritance, polymorphism).
As can be seen, in the concept of object-oriented, the core is encapsulation, abstraction, inheritance and polymorphism four characteristics. These concepts will be explained from the three dimensions of What /why/ How.
encapsulation
Encapsulation is also called information hiding or data access protection.
- Why: Protect data from arbitrary modification, improve code maintainability; Exposing only a limited number of necessary interfaces improves the ease of use of the class.
- How: A class authorizes outsiders to access internal information or data only through the means provided by the class by exposing a limited access interface (usually through
private
,public
和protected
Such as keyword control).
Here’s an example:
class Wallet {
private id: string / / wallet id
private balance: number // Wallet balance
private createTime: number // Create a timestamp
private lastModifiedTime: number // The timestamp of the last modification of the balance
constructor() {
this.id = ' ' // Generate an ID from the id generator, code omitted
this.createTime = new Date().valueOf()
this.lastModifiedTime = new Date().valueOf()
this.balance = 0
}
getId() {
return this.id
}
getCreateTime() {
return this.createTime
}
getBalance() {
return this.balance
}
getLastModifiedTime() {
return this.lastModifiedTime
}
increaseBalance(amount: number) {
this.balance = this.balance + amount
this.lastModifiedTime = new Date().valueOf()
}
decreaseBalance(amount: number) {
this.balance = this.balance + amount
this.lastModifiedTime = new Date().valueOf()
}
}
Copy the code
Let’s examine how this code is wrapped:
- There is no modification method that exposes attributes that do not need to be modified, protecting data from arbitrary modification, such as
id
和createTime
. - Only exposed the
increaseBalance
和decreaseBalance
Both methods are modified at the same timebalance
和lastModifiedTime
To prevent data from being modified and ensure service data correctness.
The example implements access control by using the private keyword to indicate that the property is private. If all attributes are public, then you can modify attributes at will with wallet. Id = 123, which is definitely not what we want.
abstract
Abstraction hides the concrete implementation of a method so that the consumer only needs to know what the method provides, not how it is implemented.
- Why: Improve the extensibility and maintainability of the code, modify the implementation does not need to change the definition, reduce the change scope of the code; It is an effective means to deal with complex systems and can effectively filter useless information.
- How: Through the interface (e.g
interface
Keyword) or abstract classes (e.gabstract
Keyword) implementation.
This is easy to understand, such as abstracting database operations (add, delete, alter) :
interface IDBOperation {
create(data: any) :any / / create
update(data: any) :any / / update
retrieve(id: string) :any / / query
delete(id: string) :any / / delete
}
class DBOperation implements IDBOperation {
create(data: any) { // ... }
update(data: any) { // ... }
retrieve(id: string) { // ... }
delete(id: string) { // ... }
}
Copy the code
In the example above, when we want to perform database operations, we only need to look at the methods exposed by the IDBOperation interface, rather than the implementation in the DBOperation class.
inheritance
Represents parent-child relationships between classes, or IS-A relationships
So if you have a mammal class in your code, you have a cat class, and the cat is a mammal, that’s an IS-A relationship, and the mammal class is the parent of the cat class, and the cat class is a subclass of the mammal class.
- Why: Solve code reuse problems.
- How: Requires programming languages to provide special syntactic mechanisms, such as Java/TypeScript
extends
And so on.
The most important aspect of inheritance is code reuse. Subclasses can reuse code from their parent classes. But inheritance is also a controversial feature. If you want to understand the functionality of a class, for example, you need to look not only at the code of that class, but also at the code of its parent class or even its “ancestor” class; At the same time, the subclass and the parent class are highly coupled, modify the parent class code, will directly affect the subclass. Therefore, we should code more with the idea of composition than inheritance.
polymorphism
A subclass replaces a parent class and invokes its implementation at run time.
- Why: Improve code extensibility and reuse.
- How: The programming language is required to provide special syntactic mechanisms such as inheritance and interfaces.
Here’s an example:
interface IExample {
prop: string
setProp(): void
}
function print(instance: IExample) {
instance.setProp()
console.log(instance.prop)
}
class Class1 implements IExample {
prop: string
setProp(): void {
this.prop = '123'}}class Class2 implements IExample {
prop: string
setProp(): void {
this.prop = '456'
}
}
print(new Class1()) / / print 123
print(new Class2()) / / print 456
Copy the code
In this example, we can print different classes of prop properties using only one print function, which increases the reuse rate of the code; At the same time, when you need to add a print type, you only need to define a class to implement IExample, and the print function does not need to be modified at all, which improves the extensibility of the code.
Object-oriented programming principles
After understanding the concepts and features, this section describes two programming principles related to object orientation: “Programming based on interfaces, not implementations” and “Combine more, inherit less.”
Programming based on interfaces rather than implementations
This principle can be very effective in improving code quality because it can be applied to separate the interface from the implementation, encapsulating unstable implementations and exposing stable interfaces.
In a word: Interfaces are stable, implementations are unstable. Interface is a function abstraction. The caller only needs to pay attention to the interface for function invocation, rather than the specific implementation. In this way, the code of the caller basically does not need to be changed when the implementation changes caused by requirements change, so as to reduce coupling and improve scalability.
Following the principle of “programming based on interface, not implementation”, we need to do the following three things:
- The naming of functions should not reveal any implementation details. For example, an upload function is currently implemented using Ali Cloud, naming should not be used
uploadToAliyun()
, should be adopted in a more abstract way, e.gupload()
. - Encapsulate the implementation details. Implementation-specific processes should be encapsulated within methods, exposing only unified interface methods. Remember that interface definitions only indicate what to do, not how to do it.
- Define an abstract interface for the implementation class. Such as interface method names, parameters required by the interface, and data types returned by the interface, should be abstracted.
Use more composition and less inheritance
As mentioned above, too deep inheritance level will lead to poor readability of code, and the high coupling between subclass and parent class will also lead to poor maintainability of code. Therefore, we should follow the principle of more combination and less inheritance in specific coding.
So, what is a combination? Let’s look at an example, for birds, some birds can fly, some birds can sing, some birds can lay eggs, different birds have different characteristics. A Flyable interface could be defined to address the behavior characteristics of the issue, and be implemented only by birds that can fly. Similarly, similar interfaces can be defined for chirping and egg-laying behaviors.
// Can fly interface
interface Flyable {
fly(): void
}
// call the interface
interface Tweetable {
tweet(): void
}
interface EggLayable {
layEgg(): void
}
/** * Ostriches can only cry and lay eggs */
class Ostrich implements Tweetable.EggLayable {
tweet(): void {}
layEgg(): void{}}/** * The sparrow can fly, sing and lay eggs
class Sparrow implements Flyable.Tweetable.EggLayable {
fly(): void {}
tweet(): void {}
layEgg(): void{}}Copy the code
We know that interfaces only declare methods and do not define implementations, so if every bird that can fly has to implement the fly() method once, and the implementation logic is the same, this will lead to code duplication. How do we solve this problem?
We can define three implementation classes for three interfaces: Code duplication is eliminated by delegate techniques such as FlyAbility, which implements the fly() method, TweetAbility, which implements the Tweet () method, and EggLayAbility, which implements the layEgg() method.
// Can fly interface
interface Flyable {
fly(): void
}
class FlyAbility implements Flyable {
fly(): void { // ... }
}
// Omit other implementations
/** * The sparrow can fly, sing and lay eggs
class Sparrow implements Flyable.Tweetable.EggLayable {
private flyAbility: FlyAbility = new FlyAbility()
fly(): void {
this.flyAbility.fly()
}
tweet(): void {
/ /...
}
layEgg(): void {
// ...}}Copy the code
conclusion
Compared with procedural programming (which uses procedures or methods as the basic unit of code organization), object-oriented programming (which uses classes as the basic unit of code organization) has the following advantages:
- Object – oriented programming is more suitable for complex program development
- Object-oriented programming has richer features (encapsulation, abstraction, inheritance, and polymorphism), which make it easier to extend, reuse, and maintain code
- Object-oriented programming languages are more semantic, human, advanced, and intelligent than procedural languages
Object-oriented programming is the theoretical basis of most design patterns. This paper summarizes the concept and characteristics of this concept, and describes two programming principles, for the future understanding of design principles and design patterns, please look forward to the following article ~
Refer to the link
- The beauty of design patterns