Interpreting design ideas from object orientation
Author: Zhe Si
Time: 2021.8.30
E-mail: 1464445232 @qq.com
GitHub: Zhe-si (Github.com)
preface
I wanted to summarize my understanding of object orientation for a long time, so I used this open class to sort out my ideas, and then wrote a cost article.
There have been a lot of introduction and understanding of the concept of object-oriented on the Internet, but few people can tell how to use object-oriented, that is, how to use object-oriented thought to design a good program. So this article will focus on “design” two words to tell this problem.
Of course, this article is just my understanding of what I have learned and done by referring to the current project. With the improvement of my knowledge and the development of technology, some of what I have written may be overturned. However, the thinking of design must be the only way to a higher position.
Note: The code used in this example uses Kotlin, a high-level Android language with advanced features that can replace Java and be compiled into a variety of types.
1. What is object orientation
First of all, let me give you a quiz question.
Xiao Ming is an ambitious programmer. One day, due to business needs, he wants to expand two new sub-data types A and B on the basis of the original data type Number. However, the operation needs to use the parent type Number for unified operation, and also need to support the call method of adding (adding) with no sequence (assuming that the adding logic is A.uma + B.namb). The sum is always of type C).
Xiao Ming’s design spirit came to his mind, and he planned not only to achieve, but also to achieve a more flexible and extensible design, but he didn’t have any good ideas, can you help him?
1.1. The meaning of object-oriented
Going back to Xiao Ming’s question, let’s get straight to the point and give the definition of object-oriented programming:
Object oriented programming is to abstract things into objects and program for the data held by objects and related behaviors.
To understand this concept, you have to start with the well-worn history of programming paradigms.
When the computer world began, there were only low-level languages, namely machine language and assembly language. The language, from the computer’s point of view, tells the computer step by step what to do first and what to do later. We need to translate our practical problems into the basic model of the computer: the memory, the arithmetic unit, the controller, the input/output device, that is, what data to store, what data to pull out and what data to perform operations on. We call this type of programming instruction programming.
Later, people in order to make programming language conforms to the understanding of people, so will be the most able to describe things enough abstract mathematical concept is introduced including again at the same time, we can define variables like a math problem, the variable phase and subtract variables (here refers to using an identifier for a data), and even define functions to represent a general operation process. In this way, we can describe real things through mathematics, and the development of things into a step by step operation process. We call this kind of programming procedural programming, which is an extension of instruction programming.
In the process of writing programs, people discovered that the essence of programming is processing data, that is, data and operations (processing of data). And there is a very obvious correspondence between the two. A set of related data always corresponds to a set of related operations. This combination satisfies the description of most things (that is, objects) in our lives. We compare the objects in the real world to the objects in the program, so that the operation of the program becomes the interaction between objects. The object becomes the basic unit in the program, encapsulates the data related to a class of objects and the operations corresponding to the data together as a class, and the object is a specific instance of the class, which is object-oriented programming.
The history of programming is one of constant abstraction to make programming fit human perception and the nature of things. This is true of functional programming and responsive programming. However, the following paradigms have not completely escaped the idea of object-oriented, and are the product of some specific scenarios. The world is made up of things, which is consistent with our basic understanding of the world, which is why object orientation has been so enduring.
1.2. Three characteristics of object orientation
There is one concept to emphasize first: type. Object orientation treats everything as an object, describes objects in terms of classes, and classes in this case, in programs, are types. We define a class of objects as a type and declare properties and methods in the type (which are characteristics of the type). It can be said that object oriented programming, from a computer perspective, is type oriented programming!
Next, we’ll talk more about object orientation. Three characteristics of object orientation are best described: encapsulation, inheritance and polymorphism.
The three can be said from three aspects of the object – oriented description. Encapsulation is the most basic performance of object orientation, inheritance is the most core behavior of object orientation, and polymorphism is the most important ability of object orientation.
1.2.1. The encapsulation
Encapsulation: The data and corresponding methods that do not need to be seen by the outside world are placed inside the class, and only the data and methods that need to be seen by the outside world are exposed.
This is the original intention and the most basic manifestation of object orientation, putting the relevant data together, putting the corresponding methods of the data together, achieving high cohesion.
At the same time, information hiding is carried out to hide the internal data and logic inside the class, so that only the data and operations corresponding to the external representation of the class can be seen by the outside world, achieving low coupling.
Here’s a classic example:
The dog
attribute behavior Name, color, length of tail Eat, bark, tail is not long
class Dog(
val name: String,
val color: String,
private val tailLength: Double
) {
private val description: String
get() = "${color}Color, tail${tailLength}Cm dog${name}"
fun eat(a) {
println(description + "Eating.")}fun shout(a) {
println(description + "Barking: Woof woof woof!")}fun isTailLong(a): Boolean {
return tailLength > 15}}fun main(a) {
// A dog instance object
val dog1 = Dog("dog1"."Black and white".12.5)
// External information and behavior exposed by dogs
println(dog1.name)
println(dog1.color)
dog1.eat()
dog1.shout()
println(dog1.isTailLong())
}
Copy the code
Here Dog attributes and behaviors are encapsulated in the Dog class.
In the current scenario, the outside world does not need to know how long the dog’s tail is, so the information about the specific length of the tail is hidden and the method to determine whether the tail is long or not is exposed. The dog’s self-description required for internal implementation is also hidden and can only be accessed indirectly through externally exposed behavior.
In this way, the outside world can access the Dog’s external information and behavior without seeing the internal implementation.
In this case, you encapsulate an entity class, putting the data and methods associated with an entity into a class. But if this is the case, there are many ways to do this, not using face objects, because real things have a very important way of describing: categorizing by features.
1.2.2. Inheritance
Inheritance: Hierarchical classification based on the common characteristics of related classes, while a concrete class contains the characteristics of an abstract class (its next classification), is an “IS-A” relationship.
This is the core behavior and symbol of object orientation. The subclass inherits the parent class, which means that the subclass “IS-A” is the parent class. The subclass obtains methods common to the subclass from the parent class, and implements personalized implementation and expansion. It is a specific category under the parent class, with features contained in the parent class, and can also have its own unique features. The parent class is a set of common features of related subclasses, which can be represented from an abstract level.
In the following example,
Dogs, orangutans, cats and rabbits are all (IS-A) animals. “Animal” is the upper level classification of those specific animals (of course, it can also be said that they are all mammals. The basis of this classification needs to be determined according to needs and actual situations), and contains the common characteristics of specific animals in the abstract level of “animal”.
animal
attribute behavior Name, color To eat,
abstract class Animal(
val name: String,
val color: String
) {
// Description is a common internal feature, but varies from instance to instance. Therefore, the subclass personalization description (otherDescription) and subtype name (typeName) should be abstracted and implemented by subclasses
protected abstract val otherDescription: String
protected abstract val typeName: String
protected val description: String
get() = "${color}color${otherDescription}the${typeName}${name}"
abstract fun eat(a)
abstract fun shout(a)
}
Copy the code
So we took the abstract features of dogs and put them into the abstract animal category,
class Dog(
name: String,
color: String,
private val tailLength: Double
): Animal(name, color) {
override val otherDescription = ", tail${tailLength}Cm"
override val typeName = "Dog"
override fun eat(a) {
println(description + "Eating.")}override fun shout(a) {
println(description + "Barking: Woof woof woof!")}fun isTailLong(a): Boolean {
return tailLength > 15}}Copy the code
And introduced a new animal category: orangutans. It is also a kind of animal, containing animal characteristics.
class Orangutan(
name: String,
color: String,
): Animal(name, color) {
override val otherDescription = ""
override val typeName = "Gorilla"
override fun eat(a) {
println(description + "Eating.")}override fun shout(a) {
println(description + "Ow! Ow!")}}Copy the code
Although we have extracted the abstract “animal” characteristics of dog and gorilla, there is no difference in the external performance of the dog or gorilla when we directly need the object. We can invoke their abstract and specific characteristics.
/ / the main ()
// Instantiate a dog directly when we need it, calling its abstract features (name, eat, etc.) and its specific features (isTailLong)
println("* * * * * * * * * * * * * * * * 1 * * * * * * * * * * * * * * * * * * *")
val dog1 = Dog("dog1"."Black and white".12.5)
println(dog1.name)
println(dog1.color)
dog1.eat()
dog1.shout()
println(dog1.isTailLong())
// The same goes for orangutans
println("* * * * * * * * * * * * * * * * 2 * * * * * * * * * * * * * * * * * * *")
val orangutan1 = Orangutan("orangutan1"."Black")
println(orangutan1.name)
orangutan1.eat()
orangutan1.shout()
Copy the code
/ / output* * * * * * * * * * * * * * * *1******************* dog1 black and white black and white tail12.5The centimeter dog Dog1 is eating black and white with a tail12.5Centimeter dog dog1 is barking: Woof woof!false* * * * * * * * * * * * * * * *2******************* orangutan1 black orangutan1 is eating black orangutan1 is saying: ow ~!Copy the code
However, when we only need to pay attention to the abstract characteristics of animals and do not care about the specific characteristics of specific animals, we can use the abstract category of “animals” to unify the reference and treatment. Dogs and orangutans are animals in an abstract sense.
// When we only need all animals, and do not need to distinguish between dogs and orangutans, we can use the superclass to refer to the concrete class and invoke its abstract common characteristics (but these abstract characteristics behave differently).
println("* * * * * * * * * * * * * * * * 3 * * * * * * * * * * * * * * * * * * *")
val animals = listOf(dog1, orangutan1, Dog("dog2"."White".15.2), Orangutan("orangutan2"."Brown"))
for (animal in animals) {
println(animal.name)
println(animal.color)
animal.eat()
animal.shout()
println()
}
Copy the code
/ / output* * * * * * * * * * * * * * * *3******************* dog1 black and white black and white tail12.5The centimeter dog Dog1 is eating black and white with a tail12.5Centimeter dog dog1 is barking: Woof woof! Orangutan1 black orangutan1 is eating black orangutan1 is saying: ow ~! Dog2 is white and has a tail15.2The centimeter dog dog2 is eating white with a tail15.2Centimeter dog dog2 is barking: Woof woof! Orangutan2 brown orangutan2 is eating brown orangutan2 is saying: ow!Copy the code
1.2.3. Polymorphism
Polymorphism: The same feature that behaves differently in different situations.
This is the most important capability of object orientation and what makes it flexible, extensible, and easy to reuse. The connotation of polymorphism itself is very broad, including overload polymorphism, subtype polymorphism, parameter polymorphism, structure polymorphism, row polymorphism and so on. From an object-oriented perspective, the most commonly used is subtype polymorphism. But whatever polymorphism it is, it fits the definition above, and it can call the same feature and produce different behavior.
For example, overloaded polymorphism, by overloading functions (functions themselves can be understood as abilities or characteristics, put into a class, that is, characteristics of that type), calls with different parameters (category, number) to get different performance.
class Number(val num: Int) {
fun add(number: Number): Int {
return num + number.num
}
fun add(number: Int): Int {
return number + num
}
}
fun main(a) {
val n1 = Number(5)
println(n1.add(6))
println(n1.add(Number(6)))}Copy the code
Subtype polymorphism is already shown in the example of inheritance. When the parent of Animal refers to different subtypes (dog and orangutan), the common characteristics of animal.eat() and animal.shout() are equally called, but different manifestations are produced, such as dog bark and orangutan cry.
This (subtype polymorphism) is a polymorphism achieved by defining a concrete subtype and calling the common characteristics of the abstract parent type. A parent type declares the common characteristics of a set of types, but does not necessarily implement them directly. It can be deferred to subtypes to implement them, resulting in different representations (polymorphisms) based on how the subtypes are implemented. The realization of polymorphism is based on inheritance.
Since we are talking about object orientation, all of the following we refer specifically to subtype polymorphism, which will be specified if we describe other types of polymorphism.
1.3. Object-oriented thinking
As mentioned above, the three characteristics describe object orientation on three levels. Encapsulation from the level of code means will be related to the data and corresponding operations together, so that the program clustering class and object basic unit; From the core behavior level, inheritance gives the ability of aggregation related features and flexible classification. Polymorphism describes the benefits, extensibility and reusability based on this classification, both in terms of performance and results.
Well, read here, presumably we have a preliminary understanding of the basic concepts and ideas of the object in the face, these knowledge is the current online more “popular” content, but also enough for everyone to interview or answer undergraduate class questions (even more confident to say, is more thorough 😎*). *
But I want to ask, do you really understand object orientation? In other words, can you use good object orientation and write good design? Or you have a good enough idea to solve itIn this section,The question that came up in the first place?
As YOU can see, this is not the end, but the beginning of the article. The idea of object oriented extensive and profound, but also constantly advancing with The Times, need rich experience and knowledge reserves, but also need us to continue to explore.
We will directly give you the basic and classic solutions to the thinking problem, and hope that you can find the problems of the current solution in different scenarios (or expanded scenarios) and generate your own new ideas as you continue reading:
// Solution: double dispatch
typealias C = Int
abstract class Number {
abstract fun add(number: Number): C
abstract fun add(a: A): C
abstract fun add(b: B): C
}
class A(private val numA: C): Number() {
override fun add(number: Number): C {
return number.add(this)}override fun add(a: A): C {
return a.numA + numA
}
override fun add(b: B): C {
return b.numB + numA
}
}
class B(val numB: C): Number() {
override fun add(number: Number): C {
return number.add(this)}override fun add(a: A): C {
return a.add(this)}override fun add(b: B): C {
return b.numB + numB
}
}
fun main(a) {
val n1: Number = A(3)
val n2: Number = B(4)
println(n1.add(n2))
println(n2.add(n1))
}
/ / output
/ / 7
/ / 7
Copy the code
So, what exactly is object-oriented thinking?
Object orientation is a programming paradigm independent of language and technology, which emphasizes the perspective and method of thinking in programming.
Back to the definition of object-oriented: Object-oriented programming is abstracting things into objects, programming for the data they hold and the behavior associated with them.
To put it simply, we need to analyze target data and operations, and reasonably abstract the world into hierarchical object structures. Each object structure is a collection of data and related operations, and a program is realized through the creation, running, interaction and destruction of objects.
This includes not only the solid objects (Animal, Dog, etc.) but also abstract objects (behavior, message, algorithm, state, etc.).
The key point here is the abstraction and classification of the world, and there are many difficulties, such as: how objects interact, the timing of destruction, and so on. It can also be seen that the thinking and understanding of the essence of the object is the most important. Only with different forms of abstraction and classification can there be other problems (such as interaction mode).
The process of abstracting and classifying the world is also the process of recognizing the same and different things. “Tao begets one, life begets two, two begets three, and three begets all things.” It can be said that it is because of difference and change that all things in the world come into being. So if you want to describe things in the world well, you have to describe how things change.
2. Program for change
In my opinion, the most important and essential part of good design is programming for change.
If you handle changes well, you will not only have good extensibility, but also improve the reusability, readability, stability, testability, compatibility and many other indicators, and even the performance will be improved.
2.1. What is change?
So what is “change”?
Change is a group of things that can change independently.
Specifically, the derivation of concrete classes with unique characteristics from abstract classes is change; The creation (or possibility of creation) of new things and the need to expand new classes is change; Change is when things need to behave differently in different situations. These three kinds of changes, summed up, is: derivative details, expand types, encapsulation difference.
2.1.1. Derivative details
Xiao Ming opened a fast food restaurant, because xiao Ming literally sell a point to eat (instant noodles), so everyone came to hello xiao Ming: boss, to eat.
Later, Xiao Ming expanded his business and also made scratch cakes. Because some customers want to eat instant noodles, and some want to eat finger cake, and “eat” is the general term of the two, can not distinguish between the two, so after the customer came into the door said: boss, instant noodles/finger cake. Or: Sir, I’d like a medium of instant noodles/hand cakes. Of course, there are some casual customers who come in and say: Boss, help yourself to something to eat.
In this example:
At the beginning, because there is no or do not need so many details, we do not relate to and distinguish specific types, so use collectively to describe.
Later, changes took place. Due to the refinement of business, the content became richer and more details became available. This general term cannot describe and distinguish the different details, and more specific categories are needed to describe the needs, and the specific categories are naturally included in the general category. Of course, there are also scenarios and needs that don’t care about these details and simply use general terms to describe them.
That’s the derivation of the details. From simple business needs at the beginning, to gradually refine the business and distinguish the details. This corresponds to the first abstract class (which may not be considered abstract at first), and then to the concrete class derived from the abstract class, which can distinguish the details.
And so from the abstract class to the concrete class, very natural response to this kind of detail derivative change. It not only solves the new need to distinguish details, but also does not destroy the original use of abstract things.
2.1.2. Expand categories
Xiao Hong also opened a fast food restaurant, her specialty is beef steamed stuffed bun, attracting many people to eat, each come in to greet Xiao Hong: boss, to a beef steamed stuffed bun.
Later, Xiao Hong also expanded business, thinking of their own beef steamed stuffed bun delicious, do other steamed stuffed bun estimates also good, so out of the new pork steamed stuffed bun. Every customer who comes in to sell pork buns can say to Xiao Hong, “Boss, have a pork bun.”
In this example:
At the beginning, the business scenarios were single, with only one type.
Later, changes have taken place, due to business expansion, new similar business, there is a new category. At the same time, due to the similarity between the two, the cost is high if the expansion is completely renewed. Therefore, the common features of such businesses are extracted as a common abstract business (abstract class), based on which to expand new businesses.
This is an extension of the genre. From the beginning of a single business needs, to gradually expand business, add business types. Correspondingly, there is only one specific category at the beginning, but it needs to be efficiently expanded later, extracting common businesses and encapsulating them into abstract classes, and expanding new specific categories based on abstract classes.
In this way, abstract classes are extracted and other concrete classes are expanded, which is a very unified and efficient way to deal with the change of category expansion. It not only solves the need to create new categories, but also supports reuse of existing common logic and interfaces.
Both examples have a specific class and need to create a new class later. Why is one derived detail and the other extended category?
First, note the derivation details in the initial abstract class referenceThere is no or need for that much detailIn this example, although instant noodles is a concrete class, it is treated as an abstract class — “food” in the current scene without distinguishing details. Of course, some scenes may only need abstract classes at the beginning (for example, at first I thought that the ones that looked like Jerry in cat and mouse were mice, but later I learned that there were specific types of mice such as hamsters and voles, and even more specific types below them).
To expand the category, we need abstract classes because we need to extract common logic and interfaces to achieve reuse.
So although both seem to implement abstract and concrete classes, they start from different points of view, and the concepts of abstract and concrete classes appear in different order.
2.1.3. Encapsulation differences
Xiao Ming’s fast food restaurant is in recession and he can hardly afford the rent. He remembered his old friend Xiao Hong’s fast food restaurant steamed stuffed bun is very hot, want to take refuge in Xiao Hong.
Xiao Hong is very kind and agrees to Xiao Ming’s request. They decided to merge the two restaurants into one and run them together without regard to you and me.
Menu:
What to eat: instant noodles, hand-scratched cakes, baozi (beef baozi, pork baozi)
In this example:
Currently, there are two similar businesses (two people’s fast food restaurant), although there are many implementation differences (one is Xiao Ming, the other is Xiao Hong, and the menu is different), but the general business format is similar (both support customers to order).
Since they are similar businesses, outsiders want to look at them uniformly and don’t want to deal with the differences themselves. And these differences are differences, they are changes.
To unify the treatment of similar businesses, their macro common business logic and presentation interfaces can be extracted and encapsulated as classes for the overall business. Implementation differences are handed over to concrete classes that implement interfaces (abstract classes).
This is the encapsulation of difference. From the beginning there are many similar businesses, to the later need to be unified treatment, hidden differences. Correspondingly, there are multiple business logic classes at the beginning, and then the same macro common business logic and interface are extracted as the overall business class, while the concrete implementation of the change is delayed to the concrete class implementing the corresponding interface.
In this way, similar business logic and presentation are extracted, and the concrete implementation of encapsulation changes are made transparent and clear to the outside to deal with the changes caused by such differences. Not only does it make differences transparent to the outside world, but it also streamlines repetitive logic and enables reuse of concrete implementations.
2.2. How to program for change
The process of programming for change is also the process of encapsulating change and adjusting the structure to support it.
In the chapter of what is change, we talked about three common changes. In practical problems, we do not always have to distinguish what is change, because the performance of change is ever-changing, and a change may belong to multiple types of change, so it is difficult to categorize clearly, let alone generalize one by one. But don’t be afraid, the above three variations cover a broad range of situations, and the rest are mostly variations of them. Secondly, the above three changes are given three typical situations, hoping to take this as an example to explain what the change is and the general method to solve the change.
2.2.1. Encapsulation changes
The first step in change-oriented programming is to encapsulate change.
Encapsulating change has three priorities: 1. Make change transparent to the outside world; 2. Reduce change to what it is; 3. Minimize the impact of change.
-
The result of encapsulating change is that it is transparent to the outside world.
Those who are interested in the details of change, we allow them to perceive change; For those of us who are not interested in the details of change, we should make it possible to treat the change as a class of things, and defer the implementation of the change to a concrete class, reducing the outside world’s understanding of the change. This not only reduces the complexity and workload of the outside world to deal with changes and understand the details of changes, but also reduces the complexity of the code. At the same time, the influence of changes is controlled in the internal scope, which is easy to find and modify.
-
The difficulty in encapsulating change is to narrow it down to what it is.
In other words, find the nature of change and encapsulate it, not the parts that do not change. Figuring out the nature of change is a skill that requires you to actually understand what is causing the change, to peel away the layers of change from the application scenario and to abstract away the real change. When we encapsulate the essential changes, we maximize the reuse of code, reduce the size of each change, and make the logic clearer.
-
Minimizing the impact of change is an important purpose of change encapsulation.
In addition to transparent encapsulation of the changing part inside, it is necessary to reduce the form of external exposure, so that behaviors and responsibilities are more single and clear. Within a changing class, the scope of influence of the changing part should be minimized as much as possible. At best, the changing class is the changing itself. Reducing the scope of change itself reflects the degree of understanding of change and the ability to abstract it. When we minimize the impact of change, we control it as much as possible.
2.2.2. Support for change
A central step in change-oriented programming is to adjust the encapsulation structure of change to support more change.
Change brings complexity, but it is also a sign of development and progress. Projects should only end because of the business, not because the code can no longer be maintained and extended. Instead of avoiding change, we should embrace it.
Once we have concentrated the changes as much as possible by encapsulating them, we can then make the changes support extension here and choose different representations in different situations. The way to support change is to extract common features and the same abstract business form (interface) and defer different implementations to concrete classes. The hard part is abstracting these features and interfaces to make them clear and flexible enough.
At the same time, in the implementation process, we try not to let the new changes affect the original logic, so that the changes are more flexible, easy to expand and reuse. The method encapsulates the change as much as possible, and puts the logic of the change into a concrete implementation class more centrally, so that the change can be self-described. Of course, if the outside world wants to use a new category, it must use the declaration of the new category. To optimize this problem, you can use code generation, dependency injection, and so on to implicitly refer to references, in addition to encapsulating new categories of references into a single place to hide display references elsewhere.
2.3. Face change in object orientation
Hidden for such a long time, finally talked about how to use object-oriented to solve the problem of change 😁. In the chapter of what is change, we said that change is to generate new business needs on the basis of the original business. There are three main situations: derivative details, expansion of types, encapsulation of differences; In how to Program for Change, we talked about abstracting, encapsulating, and extending change. But is there a sense of inexplicable familiarity? Isn’t that what we learned in object orientation?
2.3.1. Derive details from object orientation
In the case of what is change: Derived details, isn’t it inheritance to derive details from abstract classes to produce concrete classes? This variation reflects the ability of inheritance to allow a category to be personalised and extended upon its foundation. A unified call that uses an abstract class, because it points to different concrete classes, produces different representations (in different detail), is polymorphic.
Implement this example as code and specify how to program for change as follows:
Originally, Xiao Ming’s fast food restaurant, which sells food, didn’t need so many details.
class Food() {
fun sellFood(a) {
println("Boss, get me something to eat.")}}class FastFoodShopXM {
val food = Food()
fun orderFood(a) {
food.sellFood()
}
}
Copy the code
Later, the business was refined to specify the types of “food”,
open class Food {
open fun sellFood(a) {
// Omit the sequence of events that follow a random order
println("Boss, you can have anything you want.") // This represents the encapsulation of a concrete class. You can also externalize the logic without providing this default implementation and pass orderFood a random concrete class}}class InstantNoodles: Food() {
override fun sellFood(a) {
// The process of ordering instant noodles is omitted here
println("Boss, instant noodles, please.")}}class HandCake: Food() {
override fun sellFood(a) {
// Omit the following sequence of processes
println("Hey, boss, have a waffle.")}}// Adjust the original FastFoodShopXM to support new scenarios
class FastFoodShopXM {
/ / order
fun orderFood(food: Food) {
food.sellFood()
}
}
fun main(a) {
val fastFoodShopXM = FastFoodShopXM()
// Customers order different food
fastFoodShopXM.orderFood(InstantNoodles())
fastFoodShopXM.orderFood(HandCake())
fastFoodShopXM.orderFood(Food())
}
// Sir, I'd like instant noodles
// Have a scratch pie, Sir
// Whatever you want, boss
Copy the code
In this case, the change was from an abstract item to a more detailed specific item, the original Food, to the more specific InstantNoodles and HandCake.
-
Encapsulation changes: transparent to the outside
We encapsulate the details of these changes into concrete classes through inheritance, while sellFood in the abstract Food class acts as a common feature and interface for external visibility, while the concrete implementation of the changes (clicking on the concrete Food and all subsequent operations) is encapsulated into concrete classes and transparent to the outside.
-
Encapsulate change: Reduce change to what it is
Here we can implement the following:
open class FastFoodShopXMTest { open fun orderFood(a) { // There is also some additional logic for ordering food println("Boss, you can have anything you want.")}}class FastFoodShopXMTestInstantNoodles: FastFoodShopXMTest() { override fun orderFood(a) { // There is also some additional logic for ordering food println("Boss, instant noodles, please.")}}class FastFoodShopXMTestHandCake: FastFoodShopXMTest() { override fun orderFood(a) { // There is also some additional logic for ordering food println("Hey, boss, have a waffle.")}}fun main(a) { FastFoodShopXMTest().orderFood() FastFoodShopXMTestInstantNoodles().orderFood() FastFoodShopXMTestHandCake().orderFood() } // Whatever you want, boss // Sir, I'd like instant noodles // Have a scratch pie, Sir Copy the code
Here, we have expanded the category of variation to fast food restaurants, fulfilling the same requirements. Leaving aside the fact that there are multiple fast food restaurants for a particular food (this is just one obvious example), orderFood doesn’t just include food-related logic (such as ordering at the checkout), which is similar and reusable. As you can imagine, this logic is copied and pasted repeatedly across implementation classes, greatly increasing the complexity of the code (you have to cover tests separately, etc., because you can’t tell the tests that these pieces of code are the same, so the test coverage can be doubled). Even some students who are not familiar with the code will implement it repeatedly, which makes the correctness of the logic more difficult to guarantee.
Here, of course, there is a solution, which is to encapsulate other logical extracts into abstract classes and reuse them in subclasses (but who can guarantee that subclasses have called the logical methods in the right order?). . Even using the template method pattern, the food-related logic of the process is encapsulated as abstract methods to be implemented by concrete classes (here, doesn’t the logic feel more complicated? And food-related code (a single process) cannot be reused in isolation. How many classes do you have to write to cover all the cases when the number of changes in the process starts to increase? . There’s more than one way to solve a problem, but if you’re going to introduce a lot of code to solve a problem, why not just let it happen in the first place?
The small and elaborate description of the change itself is the key to its reusability, and the units we can flexibly reuse are, of course, objects or classes.
It is also a useful trick to pass parameters to get reuse units down from classes to objects. (But not discussed in detail in this article)
-
Encapsulate change: Minimize the impact of change
In this example, we encapsulate the change itself (from the abstract Food to the concrete Food) as a set of classes, while exposing only one sellFood interface externally in the Food, and only calling it externally, although with less flexibility than exposing multiple specific steps inside the sellFood. Is this flexibility necessary in this scenario?) , but let the outside understand the minimum details of the change, reduce the external impact of the change, but also make the expansion of the change more flexible and rich.
-
Support change
In this case, we extract the features with the same abstract business form (sellFood) to the abstract class Food, and different implementation delay to a concrete class, at the same time every class since the describes the concrete implementation of its change of logic, without external supplement for its description, expanding external influence small (basic need not modify the external code), Support for continued change is good.
2.3.2. Expand categories with object orientation
In the example of what is change: Extending classes, in order to extend a new class, abstract classes containing common features and interfaces are extracted as a baseline for extending the new class. Object orientation extends a new class from an abstract class through inheritance, and the polymorphism represented by inheritance is a different representation of the subclass variation part of the extension.
Implement this example as code, with a brief explanation of how to program for change (see the details of using Object-oriented derivation, as well), as follows:
Originally xiao Hong’s fast food restaurant only had beef buns,
class BeefBun {
fun sellBun(a) {
println("Boss, have a beef bun.")}}class FastFoodShopXH {
fun orderFood(beefBun: BeefBun) {
beefBun.sellBun()
}
}
Copy the code
Later, Xiao Hong expanded the baozi business, abstracted baozi, and added pork baozi,
abstract class Bun() {
abstract fun sellBun(a)
}
class BeefBun: Bun() {
override fun sellBun(a) {
println("Boss, have a beef bun.")}}class PorkBun: Bun() {
override fun sellBun(a) {
println("Boss, have a pork bun.")}}class FastFoodShopXH {
fun orderFood(bun: Bun) {
bun.sellBun()
}
}
fun main(a) {
val fastFoodShopXH = FastFoodShopXH()
fastFoodShopXH.orderFood(BeefBun())
fastFoodShopXH.orderFood(PorkBun())
}
// Sir, I'd like some beef buns
// Sir, I'd like some pork buns
Copy the code
In this case, by extracting the characteristic and the interface to the abstract class Bun, we will expand the types of change individually wrapped in each specific subclasses, changes to external transparent (external uniform Bun operation specific steamed stuffed Bun class), at the same time make changes (different kinds of steamed stuffed Bun) from description, to reduce external influence the kinds of steamed stuffed Bun, The cost of continuing to change (adding new types of steamed stuffed bun) is small.
2.3.3. Encapsulate differences with object orientation
In the case of what is change: encapsulating differences, the need to hide differences from the outside, and the need to behave differently in different situations, is not polymorphism? In order to realize polymorphism in face to object, we must use inheritance mechanism. Inheritance is a method to realize polymorphism in face to object.
Implement this example as code, with a brief explanation of how to program for change (see the details of using Object-oriented derivation, as well), as follows:
On the basis of the above code, Xiao Ming and Xiao Hong’s fast food restaurant are merged into one fast food restaurant, and the new hierarchical category of the menu is encapsulated, as well as the process of making several kinds of food by Xiao Ming and Xiao Hong respectively.
open class Food {
open fun sellFood(a) {
println("Boss, you can have anything you want.")}}class InstantNoodles: Food() {
override fun sellFood(a) {
println("Boss, I'd like instant noodles. (Made by Xiao Ming)")}}class HandCake: Food() {
override fun sellFood(a) {
println("Boss, have a waffle. (Made by Xiao Ming)")}}abstract class Bun: Food() {
final override fun sellFood(a) {
sellBun()
}
abstract fun sellBun(a)
}
class BeefBun: Bun() {
override fun sellBun(a) {
println("Sir, I'd like a beef bun. (Made by Xiao Hong)")}}class PorkBun: Bun() {
override fun sellBun(a) {
println("Boss, I'd like a pork bun. (Made by Xiao Hong)")}}class FastFoodShop {
fun orderFood(food: Food) {
food.sellFood()
}
}
fun main(a) {
val fastFoodShop = FastFoodShop()
fastFoodShop.orderFood(BeefBun())
fastFoodShop.orderFood(InstantNoodles())
fastFoodShop.orderFood(PorkBun())
fastFoodShop.orderFood(HandCake())
fastFoodShop.orderFood(Food())
}
// Sir, I'd like some beef buns. (Made by Xiao Hong)
// Sir, I'd like instant noodles. (Made by Xiao Ming)
// Sir, I'd like some pork buns. (Made by Xiao Hong)
// Have a scratch pie, Sir. (Made by Xiao Ming)
// Whatever you want, boss
Copy the code
In this example, we implement a constantly changing family of food categories through inheritance, allowing us to treat its subcategories uniformly through abstract categories. They extract and reuse common features and interfaces at different levels, and represent the polymorphism of specific food types and production processes. Thus, we encapsulate differences through object orientation.
From the above three examples, it is clear that we are using encapsulation and inheritance, which are transparent and polymorphic, to encapsulate change through this abstraction and implementation. Change, of course, is the most volatile and reusable part of the code. We accurately encapsulate change, each module is a stable part, isn’t that high cohesion? The change in the module is accurately encapsulated, can change independently and flexibly, and the change interface exposed to the outside is the smallest, then other modules naturally have no coupling, naturally achieve low coupling.
Of course, object orientation is not the only way to encapsulate change, or even the best (because there are different situations in different scenarios), but it is certainly the most widely applicable and versatile approach. In reality, however, we have the flexibility to choose the appropriate way to program for change. Note that the core is change oriented programming, and object orientation is just a means.
3. Read the six principles of object orientation
First of all, six principles of object orientation are given directly:
- Liskov Substitution Principle
- Single Responsibility Principle
- The Open Closed Principle
- The Law of Demeter, also known as the “least know Rule”
- Interface Segregation Principle
- Dependence Inversion Principle
It can be said that the above six principles, are facing the object in order to better serve the “change oriented programming”.
3.1. Richter’s substitution principle
Richter’s substitution principle: If for every object o1 of type S, there is object O2 of type T, such that the behavior of all programs P defined by T does not change when all objects O1 replace O2, then type S is a subtype of type T.
In simple terms: subclasses can always replace their parents without causing errors or exceptions
This is a principle used to regulate inheritance, which has four implications: 1. Subclasses must fully implement the methods of their parent class; 2. Subclasses can have their own implementations; 3. Input parameters can be amplified when overwriting or implementing methods of the parent class; 4. When overwriting or implementing a method of a parent class, the return value can be reduced.
Through this principle, the inheritance standard implements an “IS-A” semantics so that the parent class is a collection of common features and interfaces of subclasses, and can be referred to as a subclass at an abstract level. It is through such a standard abstraction and implementation semantics that we can extract the abstract common characteristics of change and hand over the details of change to subclasses.
3.2. Principle of single responsibility
Single responsibility principle: A class should have one and only one reason for a class to change
The single responsibility principle requires that an interface or a class can only have one cause for change, that is, an interface or a class can only have one responsibility for one thing.
By definition, this is controlling change. In change-oriented programming, we talk about encapsulating change, minimizing the change itself, and minimizing the interfaces exposed to change. We can say that for every change, there is a class, and the two are one-to-one, and the change itself is independent. An abstract class corresponds to a set of related changes.
To keep the change itself to a minimum is to tell us that each encapsulation of the change contains only one change and does not contain the logic that is independent of the change.
By keeping the interface of change exposed to the outside world to a minimum, we are abstracting the macro representation of change, treating each complete and independent function as a whole. Simply put, multiple functions of the same level should not be put together, exposing the more abstract level of the presentation interface as much as possible.
3.3. Open and close principle
Open closed principle: Classes should be open for extension and closed for modification
Open to extension and closed to modification, isn’t that the change-oriented approach to programming — the expression that supports change?
We shield external perception of internal changes by extracting abstract common features and interfaces as external presentation interfaces, extend concrete classes containing different details by inheritance, and reduce external references to concrete classes by self-description and other optimization methods as much as possible.
3.4. Know the principle at least
The least know rule: one object should know the least about other objects
The least knowable principle lets us allow as few direct references to an object as possible, and expose as few details about ourselves as possible to outsiders (including “friends” who can refer directly).
In change-oriented programming, you want to encapsulate the changes, each change corresponds to a class, and each change is referenced only in the class that has the change. At the same time, good encapsulation of changes requires us to control the external impact of changes and only provide a more abstract representation interface. Thus, we want to expose the most macroscopic manifestation of the change itself and let the outside world know the least detail of the change.
3.5. Dependency inversion principle
The dependency inversion principle: Rely on abstractions, not concrete classes
Dependencies between classes are also dependencies between changes. This principle requires us to program for interfaces, not implementations. In other words, we are required to rely on abstract classes of change (containing the common characteristics of change and the abstract representation interface), rather than concrete classes containing the details of change.
This principle is how we deal with change. Change-oriented programming requires that we care externally about the abstract representation of change, while the details of change are all encapsulated internally. Not only is it required to encapsulate change, it is also fundamental to supporting change.
3.6. Interface isolation principle
Interface isolation principle: It should not depend on interfaces it does not need, and dependencies between classes should be established on the smallest interface
This principle requires us to define exactly what change is that truly changes independently. This corresponds to one of our keys to encapsulating change: reducing change to what it is.
The independence and flexibility of change, the extensibility of the project and the reusability of the code can be ensured by understanding exactly what causes the change and identifying the common characteristics and abstract externalities of each set of changes.
4. Interpret design patterns
Here, to clarify, putting design patterns here does not mean that design patterns are part of the object facing process. The two are parallel, but the design pattern uses object-oriented in the standard implementation and conforms to the ideas of good object-oriented design (that is, conforms to the ideas of change-oriented programming). Note, however, that change-oriented programming is also object-oriented and parallel (see the summary at the end of change-oriented programming), so design patterns are often used as object-facing advanced knowledge.
4.1. Interpretation of “Use more combination and less Inheritance”
Before we dive into design patterns, let me ask you a question: Do you know why you should “use composition more than inheritance”?
This problem is directly related to everyone’s understanding of the connotation of this sentence itself.
From the perspective of the relation of things, combination is “HAS-A” relation, while inheritance is “IS-A” relation. In my opinion, things have the following five relationships to things: no relationship, “use-a”, “has-a”, “implement”, “IS-A”, strictly speaking, “implement” is a special case of “IS-A” in the pure interface case. And the relationship between things directly answers the question of which choice I’m going to use.
For example, the strategy pattern encapsulates a set of algorithms (the algorithm is the details of its changes), and the algorithm/policy is a “HAS-A” relation to the subject currently using it, using a combination. The decorator pattern, whether it is decorator or the basic implementation class to be decorated, is abstract class. Although the decorator class can continuously combine the concrete class to implement this abstract class, no matter how many layers are set, compared with the abstract class, it is “IS-A” relationship, using inheritance. Decoration classes, on the other hand, are a “HAS-A” relation to the decorated object, using composition.
From the point of view of the method of encapsulation change, there is no difference between the two, just different granularity of the problem. Composition is supported because it can change dynamically with fine granularity, showing polymorphism. But isn’t this change also implemented by the abstract class that inherits the change? Then again, object orientation is polymorphism through inheritance. And isn’t inheritance, at the level of use, composition? So, to distinguish between the two is to emphasize the granularity problem of encapsulating change, that is, to encapsulate change: to reduce change to what it is, and the specific example is to encapsulate change: to reduce change to what it is.
4.2. Interpreting “Factory”
There are three kinds of factories: simple factories (not a design pattern, but a design habit), factory methods, and abstract factories.
A simple factory can selectively produce (instantiate disease return) a product (concrete class) according to some conditions. This encapsulates the change, and more specifically, the specific reference to the change, with the factory as the only place to learn about the specific change. As a result, change can be better supported.
Factory methods, which can determine the actual production (instantiated and returned) products (concrete classes) in different implementation classes, defer the reference and selection of concrete classes to subclasses. This is also a disguised encapsulation of the specific reference to change, transparent to the external concrete change.
Abstract factories can define a set of methods for producing related products, based on which different “styles” of factories can be generated, and each factory can produce a set of related products of that “style”. This is an extension of the factory method, which can produce only one abstract kind of product, whereas the abstract factory can produce a set of abstract kinds of product. The purpose is also to encapsulate change (change as a group).
4.3. Interpreting the “Observer Model”
The observer pattern is one of the most commonly used design patterns. It is a one-to-many dependency to realize notification and synchronization of a message through subscription and publication.
So what kind of problem is the observer model dealing with? Or what kind of change are we dealing with?
As the name suggests, this is dealing with changes in the observer. The number of observers subscribing to a published topic is constantly changing, and the exact type varies. However, as long as the abstract observer class is implemented and registered with the observed, the corresponding message can be received. The abstract observer class is a collection of common characteristics and abstract external interfaces of the observer’s changes, based on which a set of concrete changes related to “observations” are generated.
4.4. Interpret design patterns
There are many more design patterns, but these are just a few examples. The principles of design patterns are: 1) favor composition over inheritance, 2) rely on interfaces over implementation, and 3) have high cohesion and low coupling. These are descriptions of programming for change. Each design pattern has its own usage scenario, and each scenario describes a common change (singleton patterns can also be considered a special change: change is limited to one instance). The way design patterns deal with these common changes is the classic way to deal with them, and is a good example of change-oriented programming.
5. From the need, to the need
Tell from the beginning in the face of the basic concept of object and ideas, to “change” oriented program (oop), is proposed in this paper how to “change” oriented programming, how to face the object oriented programming “change”, and then through the perspective of “change” interpretation oriented six principles and design patterns, object-oriented to explain and demonstrate to the status of “change”, We have explained and demonstrated the definition, method, and effect of good object-oriented design based on “change” from shallow to deep, but one thing is not explained – how to find “change”.
Finding “change” is one of the most important aspects of change-oriented programming. How to find “change”, how to identify the essence of “change” and how to determine the most abstract external performance of change are directly related to whether we can successfully encapsulate “change”, whether we can well encapsulate “change” and whether the project can continue to change flexibly.
5.1. Where do you find “change”?
To ask where all change comes from, it naturally comes from the needs!
To ask where all change goes, it must be to the needs!
It can be said that demand is change, the purpose and meaning of the project itself!
So where do you find “change”? That, of course, is finding change in requirements.
Object-oriented programming is like this. It’s not about instinct or technology per se. It’s about understanding the requirements, deciding which classes and objects interact with each other, and then implementing them.
Change-oriented programming further requires that we not only analyze the basic elements and interactions in requirements, but also find changes in requirements.
Code is corresponding to requirements. The encapsulation of changes in code is the encapsulation of changes in requirements, and the support of new changes in code is the support of changes in new requirements. We find change, encapsulate change, and support change in order to better respond to demand.
5.2. How do I detect “change”?
So how do you find “change” in requirements?
The first is to familiarize yourself with and understand the requirements, what they are, what is important, and how they change (the evolution and change history of requirements).
Next, focus on the parts of the requirement that are frequently changed, added, or deleted, and determine their scope, reasons for the change, and purpose.
Finally, according to the basic categories of changes, qualitative analysis of the changes in the requirements, and further iteration of its scope, and finally determine the changes in the requirements.
5.3. Where is the real change?
After reading how to find “change”, I think most students are still confused.
Yes, I don’t know what the real change is if I just understand the method. There are no shortcuts or tricks to spotting “change” (at least I didn’t), just continue to familiarize myself with the business and the requirements. Think also this reason, a business is not familiar with the program ape even complete a common business requirements are very strange and slow, let alone to design the project. A good architect must not only have extensive architectural knowledge and experience, but also have unparalleled familiarity and insight into the project.
But everyone learns the business slowly, and it requires a process of familiarity, understanding the business and looking for real change in the process. Don’t worry. Don’t assume that a change in requirements is a change that is intuitive. Please calmly analyze and consider whether there is a more essential form of change behind it. Of course, if you are sure, feel free to encapsulate the changes you find. For no one but God can find the most essential form of change. Or at its core, it may be that the one has no practical reference, and the most appropriate interpretation of change requires you to balance the abstract with the practical. Iterative encapsulation of change is also an important method and necessary step to encapsulate change. Many small refactorings and optimizations can lead to the discovery of larger refactoring opportunities.
6. Object-oriented bog
6.1. Excessive adoration of objects
Object orientation has been popular for years as part of programming instruction, interviews and job tests. But as a program ape, always found around or on the Internet there is excessive worship of the face of the object, excessive worship of the design mode, a word, on all kinds of methods, models piled up, and even excessive design without knowing.
Facing objects is a universal programming paradigm and idea for solving real world problems, but it is by no means a good choice for every situation, especially for a design pattern that is not even a programming paradigm or idea and only applies to some specific situations.
In the example of the fast food restaurant described in the “change oriented” programming chapter, the change is in the type and preparation of the food, or the menu. So, not to mention the more complex situation, it is completely possible to achieve the description of the menu through a configuration file, through a menu class to encapsulate the reading and parsing of the configuration file, the extensibility and flexibility is far higher than object-oriented encapsulation, while the code is more concise and clear. (Of course, I’m not self-deprecating the object-oriented example here, as the comments in the example indicate, which omits many complex steps involving multiple objects. As the complexity increases, the trade-offs between this data-oriented approach to programming and object orientation come into play.)
Of course, some people “praise” object orientation, of course, some people criticize object orientation, as if it is nothing. My view is, do not believe rumors, do not spread rumors. At the same time, I strongly agree with the idea of object orientation, which is a very good way to describe real life.
6.2. Object orientation is only part of a good solution
As can be seen in the “data-oriented” implementation of fast food restaurants, a file table is used to encapsulate the differences in food changes. But sharp-eyed students may have noticed that classes encapsulate reading and parsing of configuration files.
And as I said, for every class, there’s a change. So what change is encapsulated here? Is how to parse different configuration files. As you can see, object facing can occur in a wide variety of programming contexts, and in many cases object facing is part of a good solution, but only part of it.
6.3. Learn to design
To design good solutions, you need to learn design itself, not just any programming paradigm. Procedural, object-oriented, functional, responsive, and so on are all treated equally and used on demand. At the heart of good design, and at its core, is programming for change. To find change, go back to specific needs.
Object oriented is an abstract idea, and the way of design is more mysterious and mysterious, there is a long way to learn, need enough knowledge and experience accumulation, but also need to have good project opportunities. I hope every friend can write better code on the road to continue to forge ahead!