Progress in abstraction
A few abstractions of assembly language to the underlying machine; Imperative language is an abstraction from assembly language.
Five basic characteristics of objects
- Everything is an object
- A program is a collection of objects
- Each object has its own storage space for other objects
- Every object has a type, and the most important characteristic of a class is that it can “send life messages to it.”
- All objects of the same class can receive the same message
Object interface
How do you use objects to do something really useful? There must be a way to make a request to an object to actually do something. An interface is a specification of the behavior of an object, giving it the ability to do something.
Implement scheme hiding
Let’s say there’s a need for takeout. For the boss, the main goal is to deliver the food to the customer, while for the deliveryman, he is supposed to pick up the food, plan the route, deliver it to the buyer, only provide the boss with the ability to deliver the food (interface), all other details are hidden. Why do you do that? After hiding, the boss can’t contact and change those details, so the deliveryman won’t worry about the boss interfering with what means of transportation he uses to deliver, which can ensure that the normal delivery of takeout will not be affected.
Interfaces specify what requests can be made to a particular object, however, some code must exist somewhere in order to satisfy those requests. This code is called a “hidden implementation” of what data is hidden, and the whole issue is not complicated from the standpoint of stylized writing. A type contains functions associated with each possible request. That function is called whenever a specific request is made to the object. We often summarize this process as “sending a message” (making a request) to the object. The object’s responsibility is to decide how to react to the message (execute the appropriate code).
As with any relationship, it is important that all involved members follow the same rules. When you create a deliveryman, you create a relationship with your boss, who is also human, but whose goal is to assemble a specific platform, such as Ele. me.
There are two main reasons to control access to members. The first is to prevent backboards from interfering with deliveryman delivery behavior — often internal data type design. If only to solve a specific problem, the boss should be careful to operate the “shipping” interface. What the deliveryman provides to the boss is actually a delivery service, because the boss only cares about whether the deliveryman can help him deliver food, and does not care about how the deliveryman delivers food.
The second reason for access control is to allow the deliveryman to modify the internal structure without worrying about the impact on the boss. Let’s say the delivery guy has a girlfriend.
If anyone can use all members of A class, so the boss marki A operation can be anything, such as can’t cycle to delivery, delivery service rules to customers, distribution part B afraid marki performance more than himself, secretly put marki, A small, flat tire, so the whole platform is confused.
Java uses three explicit keywords and one implicit keyword to set class boundaries. Public, private, protected, and implicitly friendly. Keyword boundary is no longer repeated, do not understand the small partner to search.
Reuse of the scheme
Once a class has been created and tested, it should ideally represent a useful unit. But this ability to reuse is not easy to achieve, as many might expect; It requires a lot of experience and insight to design a good solution that can be reused
The easiest way to reuse a class is to just use the objects of that class directly. But you can also put an object of that class into a new class. We call this “creating a member object.” A new class can be made up of any number and type of other objects. However, as long as the new class meets the design requirements, it is ok. This concept is called “organizing” — organizing a new class on top of an existing one. Sometimes we also refer to organizing as “containment” relationships.
The organization of objects has great flexibility. The “member object” of a new class is usually set to “rpivate”, making it inaccessible to other classes. This way, we can modify those members without affecting other classes. This further increases flexibility. Inheritance, described later, does not offer this flexibility, because the compiler must place restrictions on classes created through inheritance to improve reusability.
Maybe the above paragraph is a little abstract, let’s take the delivery man’s girlfriend for example: girlfriend has the ability to cook, usually girlfriend is private (private) only cook for themselves. But if the girlfriend becomes public, delivery man B also cooks with A’s girlfriend. At this point, the problem arises. If delivery man A changes his girlfriend who can’t cook, B will have no food.
Inheritance: Reuse the interface
As such, the concept of objects can bring us great convenience. It conceptually allows us to encapsulate all kinds of data and functionality. In this way, the concept of “problem space” can be properly expressed without deliberately following the expression of the base machine. In programming languages, these concepts are reflected as concrete data types (using the class keyword).
It can be frustrating to have to build a new type that does roughly the same thing after we’ve worked so hard to make one. But it would be much better if you could take an existing data type, clone it, and add and modify it as you see fit. Inheritance is designed with this goal in mind. But inheritance is not exactly equivalent to cloning. During inheritance, if the original class (formally known as the base class, superclass, or superclass) changes, the modified “clone” class (formally known as the inherited class or subclass) also reflects the change. In the Java language, inheritance is implemented through the extends keyword
When you use inheritance, you create a new class. Not only does this new class contain all the members of the existing type (although the private members are hidden and inaccessible), but more importantly, it replicates the interface of the base class. That is, any message that can be sent to an object of the base class can also be sent to an object of the derived class as is. Based on the messages that can be sent, we know the type of class. This means that derived classes have the same type as base classes! To truly understand the meaning of object-oriented programming, we must first recognize the equivalence of this class.
For example, delivery man A was A little slow to deliver food by bicycle. In order to deliver food faster, delivery man AA (who can ride A motorcycle), the son of delivery man, is recruited now. Delivery man AA inherited the delivery ability of A and can also deliver food.
Because the base and derived classes have the same interface, that interface must be specially designed. That is, when an object receives a particular message, it must have a “method” that can execute it. If you simply inherit a class and do nothing else, the methods from the base class interface are carried directly to the derived class. This means that objects of derived classes not only have the same type, but also behave the same way, which is often undesirable.
We just said that AA, A’s son, was recruited in order to deliver takeout food faster. However, if AA only learned how to ride A bicycle to deliver takeout food from father A, the loss would not outweigh the gain. Therefore, THE AA we need must be able to ride A motorcycle.
There are two ways to distinguish a newly derived class from the original base class. The first is simple: add a new function to a derived class. These new functions are not part of the base class interface. When we do this, we usually realize that the base class is not going to meet our requirements, so we need to add more functions. This is the simplest, most basic use of inheritance, and most of the time it works perfectly for our problems. However, take a closer look beforehand to see if your base class really needs these extra functions.
Equivalence and similarity
One argument about inheritance might be: Does inheritance only improve the functions of the original base class? If the answer is yes, then the derived type is exactly the same type as the base class because both have exactly the same interface. As a result, it’s perfectly possible to replace an object from a derived class with an object from the base class! Think of it as a “pure substitution.” In a sense, this is an ideal way to proceed with inheritance. At this point, we usually think that there is an “equivalence” between the base class and the derived class — because we can confidently say that “a circle is a geometric shape”. One way to test inheritance is to see if you can fit them into this “equivalence” relationship and see if it makes sense.
But in many cases, we have to add new interface elements to derived types. So not only does it extend the interface, but it also creates a new type. This new type can still be replaced with the base type, but the substitution is not perfect because the new function cannot be accessed in the base class. We call this a “similar” relationship; The new type has the interface of the old type, but it also contains other functions, so it cannot be said that they are completely equivalent. For example, let’s consider the case of a refrigerator. Suppose our room is wired with all the controllers for cooling; In other words, we already have the necessary “interfaces” to control refrigeration. Now suppose something goes wrong with the machine and we replace it with a new type of hot and cold air conditioner that can be used in summer and winter. Cold and hot air conditioners are “similar” to chillers, but can do more. Since our rooms are only equipped with equipment to control cooling, they are limited to dealing with the cooling part of the new machine. The interface of the new machine has been expanded, but the existing system does not know anything other than the original interface.
Once you know the difference between equivalence and similarity, you’ll be much more confident when you make a substitution. Although “pure substitution” is sufficient most of the time, you will find that in some cases there are still obvious reasons to add new functionality to a derived class. From the previous discussion of these two cases, I believe you have a good idea of what to do.
Interchangeable use of polymorphic objects
Inheritance usually ends up creating a series of classes, all based on a common interface. For example, in order for a company to run run() normally, it needs to recruit three workers, and workers are all capable of working, but different people have different skills. Now the company simply calls all the doWork methods of “workers” to run. It doesn’t matter whether programmers write code or designers design.
An important thing to do with such a set of classes is to treat the object of the derived class as if it were an object of the base class. This is important because it means that we can write a single piece of code that ignores the specific details of the type and only deals with the base class. That way, the code can be separated from the type information. So it’s easier to write and it’s easier to understand. Furthermore, if we add a new type through inheritance, such as “programmer,” the code we write for the new type of “Worker” will work just as well as the old type. So the program is “scalable,” it’s “extensible.”
Dynamic binding
For example, a company operates with three employees working under control. The most surprising thing about the normal operation of run() in the company was that it was completely correct and appropriate, even though we didn’t give any special instructions. We know that the code executed when doWork() is called for programmers is different from the code executed when doWork() is called for a designer or product. However, when the doWork() message is sent to an anonymous Worker, the correct operation will be taken according to the actual connection type of Worker handle at that time. This is of course surprising, because when the Java compiler compiles code for run() for proper company running, it doesn’t know exactly what type it is operating on. While we can certainly guarantee that doWork() will eventually be called for the Worker, we can’t guarantee what will be called for a specific programmer, designer, or product. However, the action taken in the end is also correct. How does this work?
A message sent to an object that does not know its exact type but does the right thing is called “Polymorphism”. For object-oriented programming languages, the approach they use to achieve polymorphism is called “dynamic binding.” The compiler and runtime system take care of all the details; We just need to know what to expect and, more importantly, how to use it to help us design our programs. Some languages require us to use a special keyword to allow dynamic binding. In C++, the key is virtual. In Java, you don’t have to remember to add a keyword at all, because dynamic binding of functions takes place automatically. So when you send a message to an object, you can be absolutely sure that the object will take the right action, even if it involves something like up-casting.
—- Reading Thinking in Java