Java faces four characteristics of objects: encapsulation, inheritance, polymorphism, and abstract understanding

As an object-oriented programming language, the key to Java's object-oriented programming is to understand its four major characteristics: encapsulation, abstraction, inheritance, and polymorphism.

However, for these four major features, it is not enough to know their definitions. We also need to know the meaning and purpose of each feature, and what programming problems they can solve.

1. Encapsulation

First, let's look at packaging features. Encapsulation is also called information hiding or data access protection. By exposing a limited access interface, a class authorizes external parties to access internal information or data only through the methods (or functions) provided by the class.

Java has access keywords that can be used to control access rights, that is, it can implement encapsulation features. In normal code, when we define the attributes of a class, we always set the attributes of the class to private, which means that only

This class can be accessed. At the same time, we will expose some public methods that can operate these variables to external operations. The benefits of this are flexibility and understandability. You can flexibly choose which variable operations are exposed and which variables are inoperable.

And telling the caller how to operate is more in line with the object-oriented concept. This is the meaning of object-oriented.

It should be noted that encapsulation does not mean creating a class and private member variables and then directly returning the get set method of all properties. In this way, there is actually no encapsulation at all, which completely exposes all member variables and is incomprehensible.

We only need to expose the attributes that need to be set. It will be easier for developers to understand which attributes need to be set and how to set them. Then some associated attributes do not need to be exposed. We can set values ​​for their internal associations to prevent inconsistencies in external modifications due to full exposure.

The wallet example from the article I read before is easy to understand:

//Wallet class
public class Wallet {   private String id; //id   private long createTime; //Creation time   private BigDecimal balance; //Balance   private long balanceLastModifiedTime; //Last wallet balance change time   //...Omit other attributes ...




  public Wallet() {
     this.id = IdGenerator.getInstance().generate();
     this.createTime = System.currentTimeMillis();
     this.balance = BigDecimal.ZERO;
     this.balanceLastModifiedTime = System.currentTimeMillis();
  }

//Provide methods to obtain member variables

  public String getId() { return this.id; }
  public long getCreateTime() { return this.createTime; }
  public BigDecimal getBalance() { return this.balance; }
  public long getBalanceLastModifiedTime() { return this.balanceLastModifiedTime;  }

 //How to add money to wallet

public void increaseBalance(BigDecimal increasedAmount) {
    if (increasedAmount.compareTo(BigDecimal.ZERO) < 0) {
      throw new InvalidAmountException("...");
    }
    this.balance.add(increasedAmount);
    this.balanceLastModifiedTime = System.currentTimeMillis();
  }

//How to reduce money in wallet

  public void decreaseBalance(BigDecimal decreasedAmount) {
    if (decreasedAmount.compareTo(BigDecimal.ZERO) < 0) {
      throw new InvalidAmountException("...");
    }
    if (decreasedAmount.compareTo(this.balance) > 0) {
      throw new InsufficientAmountException("...");
    }
    this.balance.subtract(decreasedAmount);
    this.balanceLastModifiedTime = System.currentTimeMillis();
  }
}

We refer to the encapsulation characteristics to restrict the access methods of these four properties of the wallet. The caller is only allowed to access or modify the data in the wallet through the following six methods.

Because of this design, we know that the wallet ID and creation time should not be modified after creation, and the balance can only be added or subtracted from a practical point of view, and there is no set method.

The last wallet balance change time will only change according to the balance change and cannot be set manually, so it is handled internally. Doesn't this make it more sensible and easy to understand?

String getId()

long getCreateTime()

BigDecimal getBalance()

long getBalanceLastModifiedTime()

void increaseBalance(BigDecimal increasedAmount)

void decreaseBalance(BigDecimal decreasedAmount)

Summary: If we do not restrict access to attributes in the class, then any code can access and modify the attributes in the class. Although this seems more flexible, on the other hand,

Excessive flexibility also means uncontrollability. Properties can be modified in various weird ways at will, and the modification logic may be scattered in every corner of the code, which will inevitably affect the readability and readability of the code.

Maintainability. For example, a colleague "secretly" reset the balanceLastModifiedTime attribute in the wallet in a certain piece of code without understanding the business logic.

This will cause the two data of balance and balanceLastModifiedTime to be inconsistent.

 

2. Inheritance

One of the biggest benefits of inheritance is code reuse. If two classes have some identical attributes and methods, we can extract these identical parts into the parent class and let the two subclasses inherit the parent class.

In this way, the two subclasses can reuse the code in the parent class to avoid writing the code multiple times.

However, this is not unique to inheritance. We can also solve this code reuse problem in other ways, such as using composition relationships instead of inheritance relationships.

The concept of inheritance is easy to understand and easy to use. However, excessive use of inheritance and too deep and complex inheritance levels will lead to poor code readability and maintainability.

In order to understand the function of a class, we not only need to view the code of this class, but also need to view the code of "parent class, parent class of parent class..." layer by layer according to the inheritance relationship. Also, subclasses and parent classes are highly coupled,

Modifying the code of the parent class will directly affect the subclass. Therefore, inheriting this feature is also a very controversial feature. Many people feel that inheritance is an anti-pattern. We should try to use it as little as possible or even not at all.

So now there are many calls for using less inheritance and more combination. Java's inheritance only supports single inheritance and does not support multiple inheritance. The understanding seen on the Internet is that multiple
inheritance has side effects: diamond problem (diamond inheritance).

Assume that class B and class C inherit from class A, and both override the same method in class A, and class D inherits both class B and class C, then class D will inherit the methods of B and C, then For the methods in A overridden by B and C, which one will class D inherit?

Ambiguity arises here. Because of this ambiguity, Java does not support multiple inheritance. However, Java supports multiple interface implementations, because the methods in the interface are abstract (from JDK1.8 onwards, the interface allows the implementation of some default methods, which is not considered here).

Even if a class implements multiple interfaces, and there is a method with the same name in these interfaces, when we implement the interface, the method with the same name needs to be implemented by our implementation class itself, so there will be no ambiguity problem. .

 

3. Polymorphism

Polymorphism means that a subclass can replace the parent class and call the method of the subclass during the actual code running process. Polymorphism is generally used to achieve code scalability and reusability. The main implementation is the use of interfaces and implementation classes.

Take an example from an iterator:

In this code, Iterator is an interface class that defines an iterator that can traverse collection data. Both Array and LinkedList implement the interface class Iterator.

We support dynamically calling different next() and hasNext() implementations by passing different types of implementation classes (Array, LinkedList) to the print(Iterator iterator) function.

//Iterator interface
public interface Iterator {   boolean hasNext();   String next();   String remove(); }



//Implementation of array collection

public class Array implements Iterator {
  private String[] data;
  
  public boolean hasNext() { ... }
  public String next() { ... }
  public String remove() { ... }
  //...省略其他方法...
}

//Implementation of linked list collection

public class LinkedList implements Iterator {
  private LinkedListNode head;
  
  public boolean hasNext() { ... }
  public String next() { ... }
  public String remove() { ... }
  //...省略其他方法... 
}

public class Demo {   private static void print(Iterator iterator) {     while (iterator.hasNext()) {       System.out.println(iterator.next());     }   }   //Different types of collections implement their own different iterators, Access   public static void main(String[] args) through polymorphism {     Iterator arrayIterator = new Array();     print(arrayIterator);     Iterator linkedListIterator = new LinkedList();     print(linkedListIterator);   } }









    



Why is polymorphism scalable and reusable?

In that example, we took advantage of the polymorphic feature and used only one print() function to traverse and print data in collections of different types (Array, LinkedList). When adding another type that needs to be traversed and printed, such as HashMap,

We only need to let HashMap implement the Iterator interface and reimplement its own hasNext(), next() and other methods. There is no need to change the code of the print() function at all. Therefore, polymorphism improves the scalability of the code. If we don't use polymorphic features,

We cannot pass different collection types (Array, LinkedList) to the same function (print(Iterator iterator) function). We need to implement different print() functions for each collection to be traversed and printed.

For example, for Array, we need to implement the print(Array array) function, and for LinkedList, we need to implement the print(LinkedList linkedList) function. Using polymorphic features, we only need to implement the printing logic of a print() function to handle the printing operations of various collection data, which obviously improves the reusability of the code.

 

 

4. Abstraction


In object-oriented programming, we often use the two syntax mechanisms of interface classes (such as the interface keyword syntax in Java) or abstract classes (such as the abstract keyword syntax in Java) provided by the programming language to achieve the feature of abstraction. .

1. Why is abstraction sometimes excluded from the four major characteristics?

The concept of abstraction is a very general design idea. It is not only used in object-oriented programming, but can also be used to guide architecture design. Moreover, this feature does not require the programming language to provide special syntax mechanisms to support it.

It only needs to provide a very basic syntax mechanism of "function" to achieve abstract features. Therefore, it does not have strong "specificity" and is sometimes not regarded as one of the features of object-oriented programming.

2. What is the meaning of abstraction?

In my understanding, abstraction is a rough description of something. We ignore key details and only focus on functional points without paying attention to specific design ideas.

Abstraction, as a very broad design idea, plays a very important guiding role in code design. Many design principles embody the design idea of ​​abstraction.

For example, programming based on interfaces rather than implementation, open-closed principle (open to extensions, closed to modifications), code decoupling (reducing the coupling of the code), etc.

Guess you like

Origin blog.csdn.net/weixin_42736075/article/details/109521913