Interviewer: “Talk about object-oriented characteristics”

Code farming: encapsulation, Inheritance, and polymorphism

Interviewer: Can you tell me more about it?

Crypto: encapsulation hides the operation of a method and instead sends messages through a messaging mechanism. “Inheritance” means that a subclass inherits its parent class, which is more specific than the original class (called the parent class). This means we only need to write the same code once. Polymorphism allows objects of the same type to respond differently to the same message.

This is a typical interview scenario. Is that the right answer?

Have you ever wondered what a “trait” is?

“Property” refers to the unique properties of something!

So the question is, are “encapsulation”, “inheritance” and “polymorphism” unique to object orientation?

  • Java is an object-oriented language
  • C is a process-oriented language
  • Go is a type-oriented language
  • Clojure is a functional language

Do all four paradigms support “encapsulation”, “inheritance” and “polymorphism”?

We use examples to see if languages of four different paradigms can achieve “encapsulation”, “inheritance” and “polymorphism”!

encapsulation

Java first:

  • Java uses classes to encapsulate related methods and attributes in the same class

  • Access is controlled by access control symbols.

    public class Person { private String name;

    public String say(String someThing) { … }}

C is:

  • By means of encapsulation, the specific process is encapsulated into a method

  • Hide specific details through header files

    struct Person;

    void say(struct Person *p);

C is actually more encapsulated than Java! Because the structure in Person is hidden!

At first glance, the Go language looks like it’s wrapped in functions, but functions are actually a type in the Go language. So you can say that the Go language is encapsulated in types.

func say(){
 fmt.Println("Hello")
}

func main() {
 a := say
 a()
}
Copy the code

Clojure encapsulates primarily in the form of functions.

(defn say []
 (println "Hello"))
Copy the code

As you can see, all four languages support encapsulation, just in different ways.

inheritance

Inheritance, in fact, is code reuse.

Inheritance itself is a separate feature that has nothing to do with a class or namespace. It’s just that object-oriented languages bind inheritance to the class level, whereas object-oriented languages are more general and emphasize inheritance, so when we say inheritance, by default we mean class-based inheritance.

Java is class-based inheritance. That is, subclasses can reuse non-private properties and methods defined by their parent class.

class Man extends Person {
 ...
}
Copy the code

C can be reused through Pointers. Similar to the following Go language, Go is relatively simple. C is more of a trick!

struct Person { char* name; } struct Man { char* name; int age; } struct Man* m = malloc(sizeof(struct Man)); m->name = "Man"; m->age = 20; struct Person* p = (struct Person*) m; // Man can be converted to PersonCopy the code

The Go language implements inheritance through anonymous fields. That is, one type can reuse fields or functions of another type through anonymous fields.

type Person struct { name string } type Man struct { ... Person // Import fields in Person}Copy the code
  • Man can reuse fields in Person by introducing Person directly into the definition
  • In Man, the name field can be accessed either through this.person. name or directly through this.name
  • If Man has name, then this.name is Man’s name, and the name in Person is overwritten
  • The same scheme applies to functions

In Clojure, code reuse is achieved through higher-order functions. You simply pass the function you want to reuse as an argument to another function.

; Reuse print function (defn say [v] (println "This is "v)); This is Man (defn Man [s] (s "Man")); Print This is Women (defn Women [s] (s "Women"))Copy the code

Clojure can also implement ad-hoc inheritance, which is symbol – or keyword – based inheritance and is more applicable than class-based inheritance.

(derive ::man ::person)
(isa? ::man ::person) ;; true
Copy the code

As you can see, all four languages can also implement inheritance.

polymorphism

Polymorphism is actually a dynamic selection of behaviors.

Java implements polymorphism through a superclass reference pointing to a subclass object.

Person p = new Man();
p.say();
p = new Woman();
p.say();
Copy the code

C language polymorphism is realized by function Pointers.

struct Person{ void (* say)( void ); } void man_say(void){printf("Hello Man\n"); } void woman_say( void ){ printf("Hello Woman\n"); }... p->say = man_say; p.say(); // Hello Man p->say = woman_say; p.say(); // Hello WomanCopy the code

The Go language implements polymorphism through interfaces. Interface here is not the same as interface in Java

; Type Person interface {say()} type Man struct {} type Women struct {} func (this Man) say() { fmt.Println("Man") } func (this Women) area() { fmt.Println("Women") } func main() { m := Man{} w := Women{} exec(m) // Man say exec(w) // Women say } func exec(a Person) { a.say() }Copy the code
  • Instead of implementing the interface as in Java, Man and Women define the same methods as in Person
  • The exec function receives interface as an argument

In addition, Clojure can implement polymorphism through higher-order functions (the above examples are examples of higher-order functions). Polymorphism can also be achieved through “multiple methods”.

(defmulti say (fn [t] t)) (defmethod run :Man [t] (println "Man")) (defmethod run :Women [t] (println "Women")) (rsay :Man) ; Printing Man, combined with ad-hoc, enables Java-like polymorphismCopy the code

All four languages are equally capable of polymorphism.

Problem solving

From the above comparison, “encapsulation”, “inheritance” and “polymorphism” are not unique to object orientation!

So what are we really talking about when we say “program to XX”?

Let’s answer this question in a problem-solving way!

For some very simple problems, we can usually get the solution directly. For example: What is 1+1?

But for more complex problems, we don’t have direct solutions. For example, there are a number of chickens and rabbits in the same cage, counting from the top, there are 35 heads, counting from the bottom, there are 94 feet. How many chickens and how many rabbits are there in the cage?

The general approach to such problems is to model the problem abstractly and then look for solutions to the abstractions.

Corresponding to software development, for real environment problems, we first through programming technology to its abstract modeling, and then solve these abstract problems, and then solve the actual problems. The abstraction here is “encapsulation”, “inheritance”, “polymorphism”! Class-based implementations, type-based implementations, and function-based or method-based implementations are abstract concrete implementations.

Now back to the question: what are we really talking about when we say “program to XX”?

In fact, it’s about using different kinds of abstractions to solve problems.

Abstract way: just different, no advantages and disadvantages

Whether it is object-oriented programming or functional programming or procedural programming, it is just the difference in the way abstraction is carried out, and the difference in the way abstraction is carried out leads to the difference in the way problem solving is carried out.

  • Object orientation abstracts reality into objects and communicates between objects to solve problems.
  • Functional programming abstracts reality into finite data structures and functions that operate on these data structures. Problems are solved by functions operating on data structures and by calls between functions.
  • Procedural programming abstracts reality into data structures and procedural methods, and solves problems through method combination calls and operations on data structures.

Each method of abstraction has both advantages and disadvantages. There is no perfect abstraction.

For example, with object orientation, it is easy to customize classes by adding types, but it is difficult to implement an existing set of abstract methods (called the expression problem) for existing concrete classes without modifying the defined code. Functional programming, on the other hand, makes it easy to add operations (that is, functions), but it is difficult to add a type that fits the various existing operations.

For example, in Java, the String class didn’t have an isEmpty method before 1.6. If we want to determine whether a String isEmpty, we have to use the utility class or just wait for the official supply, and the ideal method would be “ABC”.isempty (). While modern languages offer various means to solve this problem, dynamic languages such as Ruby can be implemented with monkey patches; Scala can be implemented through implicit transformations; Kotlin can be implemented using the intern method. But that’s the problem with object-oriented abstractions.

For functional languages, such as Clojure, if you want to add a similar function, you can simply write a corresponding function because its data structure implements a unified interface: Collection, Sequence, Associative, Indexed, Stack, Set, Sorted, etc. This allows a Set of functions to be applied to sets, lists, vectors, and maps. On the other hand, if you were to add a data structure, you would need to implement all of the above interfaces, which is incredibly difficult.

The degree of abstraction is positively correlated with maintenance cost

The advantage of object orientation over other abstractions may be that it is relatively granular and easy to understand.

It’s like building a computer:

  • Object orientation is like taking a computer apart into motherboard, CPU, graphics card, case, etc., and you can put it together with a little learning.
  • Functional programming is like taking a computer apart. You have to learn the knowledge, but you also have to assemble the components. You can imagine the difficulty. But the combinations and degrees of freedom are much better than object-oriented.

The more abstract it is, the harder it is to understand. But the higher the abstraction, the more adaptable it is and the cleaner the code is.

The higher the level of abstraction, the finer the corresponding granularity of abstraction. The finer the abstraction, the greater the flexibility, but also the greater the maintenance difficulty.

conclusion

Programming in mind! Programming paradigms are just tools to help you think! Don’t be limited by programming paradigms! What you need to consider is “How do I implement XXX using the XX programming paradigm?” “Rather than” What can the XX programming paradigm achieve?”

Each approach to abstraction has its own advantages and disadvantages. Each programming paradigm has its own best practices to complement each other. These best practices are collected and codified into routines or patterns.

For example, in object-oriented:

  • Reuse code prioritizes composition over inheritance
  • Polymorphism inherits
  • Design principles
  • 23 design patterns
  • .

The resources

  • Clean architecture
  • Wiki:zh.wikipedia.org/wiki/%E9%9D…