Object-oriented programming and object-oriented languages are characterized by encapsulation, abstraction, inheritance and polymorphism. In general, we know these features, but we don’t know how or why to use them. Now, let’s solve these problems together.

encapsulation

Encapsulation is also called information hiding or data access protection. By exposing a limited access interface, a class authorizes outsiders to access internal information or data only through the means (or functions) provided by the class.

So what exactly is encapsulation? Let’s use a simple example to describe the encapsulation feature.

The following is a code implementation of equipment enhancement in the game. When the player obtains equipment, we create Equip objects for each item to save the data of each item.


/**
* 装备
*/
public class Equip {
    // The equipment flow id
    private int id;
    // Device configuration ID
    private int configId;
    // enhance the moment
    private long upgradeLevelTime;
    // Increase the level
    private int level;
    // More attributes.private Equip(a) {}
    
    public Equip(int configId) {
        this.id = IdGenerator.obtainId();
        this.configId = configId;
        this.level = 1;
    }
    
    /** * id */
    public int getId(a) { 
        return id; 
    }
    
    /** * Device configuration id */
    public int getConfigId(a) { 
        return configId; 
    }
    
    /** ** /
    public long getUpgradeLevelTime(a) { 
        return upgradeLevelTime; 
    }
    
    /** ** Obtain a level of enhancement */
    public int getLevel(a) { 
        return level; 
    }
    
    /** * strengthen *@returnSuccessful reinforcement */
    public boolean upgradeLevel(a) {
        // Determine if it can be improved to the next level.// Reinforce success
        this.level ++;
        this.upgradeLevelTime = System.currentTimeMillis();
        return true; }... }Copy the code

From the code, we can see that the equipment has properties:

  • Id Id of the equipment flow
  • ConfigId Indicates the device configuration ID
  • UpgradeLevelTime Upgrade time
  • Level of reinforcement

Method of existence:

  • GetId () Indicates the id of the equipment flow
  • GetConfigId () Indicates the device configuration ID
  • GetReceiveLevelTime () Hardening time
  • GetLevel () gets the level of enhancement
  • UpgradeLevel () improved

Equipment pipeline ID, equipment configuration ID, these attributes why only get method? Because these methods assign values in the constructor during the initial construction of the object, solidify values, and do not allow subsequent changes, they should not be provided.

UpgradeLevelTime upgrade time and upgradeLevel both provide the get method to get the data and the upgradeLevel will be upgradeLevel. Why? For all of this design, the enhancement moment and the enhancement level of the two properties of the change will occur in the enhancement operation, therefore should not provide set method modification.

To encapsulate this feature, we need the programming language itself to provide certain syntactic mechanisms to support it. This syntactic mechanism is access control. The keywords private and public in this example are the access control syntax of the Java language. Properties modified by the private keyword can only be accessed by the class itself, protecting them from direct access by code outside the class. If the Java language does not provide an access-control syntax and all attributes are public by default, then any external code can pass a value like equip. Id =30; This way of directly accessing and modifying attributes, there is no way to achieve the purpose of hiding information and protecting data, and it does not support encapsulation features.

What is encapsulation? How to encapsulate? We’ve worked it all out up there. So what programming pain points does encapsulation solve?

If the access to the attributes in the class is not restricted, any code can access or modify the attributes in the class. Although the use of this class is more flexible, but this “flexibility” has to pay a price, resulting in uncontrollable state of the class, seriously affecting the readability and maintainability of the code. As in the above example, if upgradeLevelTime and level both provide set methods, a partner will manually set the upgradelTime (0L) without knowing it, which will result in inconsistent data between upgradelTime and upgradelLevel. Since the reinforcement moment attribute is set, the modification itself depends on the reinforcement operation, which must be illegal.

In addition, classes only expose the necessary operations through a limited number of methods, which also improves the ease of use of classes. If you expose all of the attribute interfaces, it’s easy to make mistakes unless the caller has a good understanding of the business of this class. This is extremely unfriendly to callers, and to reduce the probability of errors, you should only expose interfaces that are really needed. If we now have an electric kettle, and the kettle has a lot of buttons on and off, the user has to spend a lot of time studying the instructions and not necessarily understanding them at the end, it’s too noisy. For users, when I get an electric kettle, I just want to plug it in, start boiling, and get hot water as soon as possible. Simple on and off buttons are sufficient for user scenarios, with high efficiency, good user experience and low error rate.

abstract

Encapsulation addresses the problem of hiding information and data protection, while abstraction addresses the problem of hiding method implementation.

In object-oriented programming, abstraction is often achieved through two syntactic mechanisms provided by programming languages: interface classes (such as Interface in Java) or abstract classes (such as Abstract in Java).

We can see the abstraction in the following example:

public interface IUserService {

    /** * login */
    void login(User user);
    
    /** * log out */
    void logout(User user);
    
    /** ** */
    void rename(User user);
}

public class UserService implement IUserService {

    @Override
    public void login(User user){
        / /...
    }
    
    @Override
    public void logout(User user){
        / /...
    }
    
    @Override
    public void rename(User user){
        / /...}}Copy the code

Abstraction does not have to be implemented through interfaces or abstract classes. Even if you don’t write the IUserService interface class, the UserService class itself can satisfy the abstract characteristics. Because the methods of a class are implemented through the syntactic mechanism of “functions” in a programming language. Wrapping concrete implementation logic in functions is itself an abstraction. When we use a function, we don’t need to know how to implement it inside the method, just call it.

What is abstraction? How do you abstract? We’ve worked it all out up there. So what programming pain points does abstraction solve?

When we deal with a complex problem, the implementation details of the problem need not be exposed to the caller, we need to do the upper level abstraction, abstraction of the non-essential implementation details, so that we can better focus on the functionality, rather than the concrete implementation design ideas. We need to think abstractly when naming the methods of our classes and not expose too many implementation details to ensure that we don’t have to change the method definition again when we later change the internal logic. For example, getMySqlDBUrl() exposes the implementation details. If the database is changed to MongoDB, the method definition and implementation are inconsistent, and the method needs to be redefined. GetDBUrl () is more appropriate.

In the code design, play a very important role of guidance. Many design principles embody this idea of abstraction, such as programming based on interfaces rather than implementations, the open and closed principle (open to extensions, closed to modifications), and code decoupling (reducing code coupling).

inheritance

If you are familiar with object-oriented programming languages such as Java and C++, you should be familiar with this feature. Inheritance is used to represent is-A relationships between classes.

To implement this feature, programming languages need to provide special syntactic mechanisms to support it, such as Java’s use of the extends keyword for inheritance and C++’s use of colons (class B: Public theses A), Python using parentheses (), Ruby using <. However, some programming languages support single inheritance but not multiple inheritance, such as Java, PHP, C#, Ruby, etc., while some programming languages support both single inheritance and multiple inheritance, such as C++, Python, Perl, etc.

What is inheritance? How do you inherit? We’ve worked it all out up there. So what programming pain points does inheritance solve?

One of the biggest benefits of inheritance is code reuse. If two classes have the same properties and methods, we can extract those same parts into the parent class, and let the two subclasses inherit from the parent class. In this way, both subclasses can reuse the code in the parent class, avoiding the code being written more than once.

Inheritance is well understood and easy to use. However, excessive use of inheritance, which is too deep and complex, can lead to poor readability and maintainability of code. Some people think inheritance is an anti-pattern and should be used sparingly, or even not at all. In the case of too deep inheritance level, the interface-combination-delegate approach is better than inheritance, which is the idea of “more combination and less inheritance”.

polymorphism

Polymorphism means that a subclass can replace its parent class and call its method implementation during the actual code run.

We can better understand polymorphism by using the following example:

public interface Animal {
    
    void eat(a);
    
    void sleep(a);
}

public class Dog implement Animal {

    @Override
    public void eat(a){
        / /...
    }
    
    @Override
    public void sleep(a){
        / /...}}public class Bird implement Animal {

    @Override
    public void eat(a){
        / /...
    }
    
    @Override
    public void sleep(a){
        / /...}}public class Demo {
    public static void main(String[] args){
        Dog dog = new Dog();
        Bird bird = new Bird();
        
        eat(dog);
        sleep(bird);
    }
    
    private static void eat(Animal a) {
        a.eat();
        // ...
    }
    
    private static void sleep(Animal a) {
        a.sleep();
        // ...}}Copy the code

Polymorphism has three mechanisms:

  • Superclass objects can refer to subclass objects. Both Dog and Bird can be passed to Animal.
  • The relationship between objects is inheritance. Dog and Bird inherit from Animal.
  • Subclasses can override methods of their parent class. In this case, Animal is the interface, or it can be an abstract class.

We pass objects to Animal through different implementation types, and when we call Animal’s methods, we’re essentially calling the method that passes the object to implement the logic. Specifically, when we call Animal#eat, the implementation logic of Dog#eat is invoked within the method.

What is polymorphism? How do YOU do polymorphism? We’ve worked it all out up there. So what programming pain points does polymorphism solve?

Polymorphism improves the extensibility and reusability of code.

Why can scalability be improved? In the example above, Demo#eat and Demo#sleep use the same method to handle calls from Dog and Bird, and the implementation logic of Demo#eat and Demo#sleep does not change when adding Animal classes. This improves the extensibility of the code.

Why can you improve reusability? We do not need to implement different types of functions (Demo#eat(Dog), Demo#eat(Bird)), we only need a function processing (Demo#eat(Animal)), obviously improve the code reuse.

In addition, polymorphism is the code implementation basis for many design patterns, design principles, and programming techniques, such as policy patterns, interface-based programming instead of implementation, dependency inversion, in-substitution, and using polymorphism to get rid of lengthy if-else statements.

review

  • encapsulation
    • What? Information hiding or data access protection.
    • How? Access control through syntactic mechanisms exposes limited interfaces.
    • According to? Readability, maintainability, ease of use.
  • abstract
    • What? Hide the implementation of the method.
    • How? Interface class or abstract class, not required.
    • According to? Hide implementation details and reduce complexity.
  • inheritance
    • What? Represents between classesis-aRelationship.
    • How? Supported by special syntactic mechanisms, such as Java’s use of extends.
    • According to? Code reuse.
  • polymorphism
    • What? By binding a subclass to a parent class, the parent class can call the subclass implementation logic.
    • How? Provides special syntax mechanisms. Such as inheritance, interface classes.
    • According to? Improved code scalability and reuse.