The meaning of three features of object orientation

  • 2020-06-12 10:08:47
  • OfStack

Three features of object orientation: encapsulation, inheritance, and polymorphism. This is something that can be found in any object-oriented design book, but it's rarely explained clearly, and beginners don't really understand it beyond memorizing a few concepts. A few days ago, I read the SOLID principle explained by Uncle Bob on youtube, in which I mentioned 3 features of object orientation in one paragraph. I gained a lot, but I do not completely agree with his views. Here is my idea:

encapsulation

Encapsulation level 1 means information hiding. This is a textbook way to hide the implementation details of a class or module and provide only the smallest interface, known as the "minimum knowledge principle." There is a common understanding that a normal programmer can understand about 10,000 lines of code. This is the size of code that a normal programmer can understand without understanding the implementation details. For example, if you have a file system, FAT, NTFS, EXT4, YAFFS2, etc., their implementation is relatively complex, ranging from a few thousand lines of code to tens of thousands of lines of code, it is difficult to understand their internal implementation, but if you mask their internal implementation details, just to understand their external interface, it is very easy.

Uncle Bob made a surprising observation about this layer of "encapsulation" : "Encapsulation" is not an object-oriented feature, and the process-oriented C language is better at "encapsulation" than the object-oriented C++/Java! The evidence is strong: the C language divides functions into internal functions and external functions. The internal functions are decorated with static and placed in the C file and the external functions in the header file. You have no idea that internal functions exist, and even if you did, you wouldn't be able to call them. And like in C + + / Java, through public/protected private/friend keywords, such as the function, or attribute is divided into different levels, this expose internal details to the user, user can even bypass the compiler restrictions to call a private function. So in terms of information hiding, "encapsulation" is not an object-oriented feature, but object-oriented weakens "encapsulation".

The second meaning of "encapsulation" is to encapsulate data and behavior in one. I think that's what "encapsulation" in object-oriented is all about, and it's not mentioned or emphasized in the 1 textbook. In procedural programming, data and behavior are separated, whereas object-oriented programming treats them as an organic whole. So, in this sense, "encapsulation" is really an object-oriented "property."

Object orientation is a way of thinking, not a form of expression. In THE C language, object-oriented programming can be implemented, in fact, almost all C language development of large projects, are developed with object-oriented ideas. It would be unfair to say that C is a process-oriented language, and that object-oriented programming is more about guidelines than programming languages. You can write procedural code in C++/Java or object-oriented code in C.

inheritance

A class is a standard of classification, that is, a class of things, a class of abstractions that have the same properties and behave as objects. An animal, for example, is a class that describes the set of things that have the property of an animal. The dog is also a class, it has all the characteristics of animals, we say that the dog class inherits the animal class, the animal is the parent of the dog, the dog is the child of the animal. The effects of inheritance can also be simulated in C languages, such as:


struct Animal {
...
};

struct Dog {
  struct Animal animal;
  ...
}

struct Cat {
  struct Animal animal;
  ...
}

Since the C language also implements inheritance, Uncle Bob does not consider inheritance to be an object-oriented "feature." But In my opinion, the way inheritance is implemented in C requires object-oriented thinking to understand, otherwise the above example would be quite different from pure data structure: animal is a member of Dog, so Animal can be viewed as a part of Dog! Is a becomes has a. It is only in object-oriented thinking that "inheritance" makes sense, so it is not far-fetched to say that "inheritance" is a "property" of object orientation.

Implementing multiple inheritance in C is even more difficult, remember that glib implemented multiple inheritance of interfaces, but it was awkward to use and even more difficult for beginners to understand. Multiple inheritance in some cases, can cause ambiguity, to the compiler than diamond inheritance structure: A is a base class, B and C are its two subclasses, D inherited from B and C, if B and C overloading A one function, the compiler can't distinguish between use B or C (this can be resolved, of course).

As Uncle Bob said, Java does not implement multiple inheritance, not that multiple inheritance is useless. Rather, to simplify the compiler implementation, C# does not implement multiple inheritance because Java does not :)

In addition to the fact that multiple inheritance of interfaces is essential, multiple inheritance of classes is common in reality. For example, wolves and dogs are subcategories of dogs, while cats and tigers are subcategories of cats. Both dogs and cats are subclasses of animals. But cats and dogs are domestic animals, tigers and wolves are wild animals. A cat inherits not only the characteristics of a feline but also those of a domestic animal. Class is the standard of classification, and the mixing of different classification standards is the main source of multiple inheritance. Multiple inheritance can be implemented in other ways, such as traits and mixin.

Whether it's plain inheritance, interface inheritance, or multiple inheritance, it's much easier and more intuitive to implement in object-oriented programming languages. In process-oriented languages, it can be implemented, but it's ugly, and it's essentially an object-oriented way of thinking. So inheritance should be called an object-oriented "property". Because of the complexity brought by inheritance, composition is recommended to replace inheritance in modern object-oriented design.

polymorphism

Polymorphism is supposed to be the most important (and certainly not unique) property in object-oriented thinking, but the textbook only introduces the manifestation of polymorphism, not its purpose and value. "Polymorphism" generally manifests itself in two forms:

Functions of the same name that allow different input parameters exist. This property brings the convenience of a definite value, especially overloading constructors and operators. But this "polymorphism" is determined at compile time, so it only counts as a syntactic sugar, and has no particular meaning.

A subclass can override a function of the same name whose function prototype is identical in the parent class. If you just look at what it looks like, a function that exists in a superclass can be reimplemented in a different subclass, it looks like you're stuffed. But this "polymorphism" is fundamental to software architecture, and almost all design patterns and approaches rely on this feature.

Isolating change is one of the fundamental goals of software architecture design, and interfaces are the most important means of isolating change. We often talk about separating interfaces from implementations, and programming against them, mainly because interfaces can isolate change. If there is no second "polymorphism," there is no real interface. An object-oriented interface is not only a set of functions provided by a module, but also a set of functions that are only bound to a specific implementation at runtime, without being known at compile time. Let's think of interfaces simply as defining a set of functions in a base class, but instead of implementing them, the base class implements them in a concrete subclass. This is the second manifestation of polymorphism.

How can an interface isolate change? Uncle Bob gives a very good example:


#include <stdio.h>
int main() {
  int c;
  while((c = getchar()) != EOF) {
    putchar(c);
  }
  return 0;
}

This program is one level from Hello world, you type one character from the keyboard, it displays one character. But it contains the most subtle form of polymorphism. For example, getchar reads one character from standard input (STDIN). Keyboard input is the default standard input, but keyboard input is just one of many standard inputs (STDIN). You can read data from any ONE IO device: network, files, memory, serial port, etc., and replace it with any one input without any changes to the program.

The implementation changes, the caller does not need to modify the code, and it does not have to recompile or even restart the application at all. This is the power of interfaces, and the result of polymorphism.

How does the above program do that? The IO device is driven by a set of interfaces that define open, close, read, and write operations. For implementers, it doesn't matter where the data comes from or where it goes, just implement the functions defined in the interface. For the consumer, it's completely different from how it's implemented.

"Polymorphism" is the basis not only for isolating change but also for code reuse. Reuse of common functions is valuable and easy to achieve in process-oriented development. But in reality reuse is not so simple, even some masters also lamented reuse too difficult. For example, you might need the class A, and when you take it over, it has dependencies on B, B has dependencies on C, and end up relying on a seemingly unrelated class, so the idea of reuse has to stop. If you think you're exaggerating, you can take its B+ tree code from a database (like sqlite) and use 1.

With the help of polymorphism, the situation is very different. The A class depends on the B class, so we can define B as an interface for users of the A class to pass in, which is called dependency injection. If you want to reuse the A class, you can customize an implementation of the B interface for it. For example, I recently in a only 8 K memory hardware, 1 piece of norflash wrote a simple file system (and as A class), if I go directly to invoke the norflash API (and as B class), can let a file system (A class) and norflash API (B class) tightly coupled to 1, this will make the reuse of the file system.

My approach is to define an interface for a block device (that is, the B interface) :


typedef unsigned short block_num_t;
struct _block_dev_t;
typedef struct _block_dev_t block_dev_t;
typedef block_num_t (*block_dev_get_block_nr_t)(block_dev_t* dev);
typedef bool_t (*block_dev_read_block_t)(block_dev_t* dev, block_num_t block_num, void* buff);
typedef bool_t (*block_dev_write_block_t)(block_dev_t* dev, block_num_t block_num, const void* buff);
typedef void  (*block_dev_destroy_t)(block_dev_t* dev);
struct _block_dev_t {
  block_dev_get_block_nr_t  get_block_nr;
  block_dev_write_block_t  write_block;
  block_dev_read_block_t   read_block;
  block_dev_destroy_t    destroy;
};

When the file system is initialized, block devices are injected:


bool_t sfs_init(sfs_t* fs, block_dev_t* dev);

Thus, the file system only interacts with the block device interface and does not care whether the implementation is norflash, nandflash, memory, or disk. And there are a couple of added benefits:

You can unit test the file system on PC. On PC, the file system works properly by simulating a block device with memory.

Wear equalization algorithms and bad block management algorithms can be added to block devices through decorative mode. These algorithms and file systems can be reused independently.

Polymorphism makes true reuse possible; without polymorphism there would be no frameworks. In C, polymorphism is achieved through function Pointers, in C++ via virtual functions, in Java there are specialized interfaces, and in JS, a dynamic language, each function is polymorphism. Polymorphic is not an "object specific" property, but object-oriented programming languages make it simpler and safer.

conclusion


Related articles: