Golang's "two-way inheritance" makes programming more elegant

1. Background

When the author developed a distributed system, the raft algorithm was implemented on the management side to avoid a single point of failure of the entire system. The general description of the entire system is as follows:

  1. The state of all nodes on the management side is consistent, so it is more appropriate to use peer to define the management side node; 
  2. A leader is elected among all nodes on the management side. All decisions related to system management are issued by the leader. The peer synchronizes the decision of the leader so that all peer states are consistent. When the peer where the leader is located is abnormal, restart The elected leader can continue to make decisions on the basis of the previous leader;
  3. One point to note: The leader's decision needs to pass through the proposal (propose) of more than half of the peers before it can be applied by the peer, so from the beginning of the leader's decision to the entire system confirming the success of the decision-making execution, it needs to go through several times. Process, we
  4. This is simply described as an asynchronous process;

This article does not discuss the content related to raft, but uses raft to introduce the concept of peer and leader. According to the above description, how should the class be defined using the phase object programming method?

2. Analysis

The more conventional method should be: the leader is a peer with more attributes and connections, so the leader should inherit from the self peer, then the code (C++) is defined as follows:

// 定义peer类
class Peer {
public:
    Peer(){}
    ~Peer(){}
public:
    void PeerMethod(void) {}
private:
    int peerVariables;
}
// 定义leader类,继承自peer
class Leader : public Peer {
public:
    Leader(){}
    ~Leader(){}
public:
    void LeaderMethod(void) {}
private:
    int leaderVariables;
}

Next, let's see if this definition method will encounter trouble in the implementation process. Let's analyze it from the following three perspectives:

  1. Leader view: The object is constructed by the leader class, and the connections and attributes serve for decision-making. Because the self-peer is inherited, the current system state is automatically obtained by inheriting the peer;
  2. Peer view: The object is a peer-type structure, and the connection and attributes are related to the synchronization system state, and there is no need to deal with decision-making issues, so you can do whatever you want;
  3.  Leader peer view: The above two views are still not well understood, so what is a leader peer? It is the leader among peers!

Regarding the leader peer, many of my fellow partners will definitely not be able to sit still. Isn't this nonsense? Isn't it the same as the leader? This starts from the actual scenario. Before the leader is elected, all nodes are still peers. At this time, the service object is constructed by the peer; when a peer is successfully elected as the leader, then the service object is provided. It should be constructed by a leader. Remember that the leader is also a peer, so objects constructed by the leader have both peer and leader capabilities.

So the question is, what type of object should be used to provide services? From the above-mentioned inheritance relationship, it is relatively more reasonable to use peer type objects, and objects of subclasses can be assigned to objects of class classes. However, when the peer needs to switch to the leader status, regardless of whether it is C++ or JAVA, more or less mandatory conversion statements must be added, the peer object is assigned to the leader object, and then the leader object is used for execution. Some operations. As shown in the following code:

{
    Leader *leader = (Leader*)peer;
    leader->LeaderMethod();
}

When the author didn't touch golang before, I felt that the above code couldn't be more normal. After I defined the so-called "two-way inheritance", I felt that the above code was not elegant enough. The so-called two-way inheritance means that two types inherit each other, which is unthinkable in C++ or JAVA. A class A is a subclass of class B and a subclass of class B. Ethically, it doesn’t make sense. It is also impossible to achieve. But this can be done in golang, as shown in the following code:

// 定义Peer
type Peer struct {
    *Leader
    peerVariables int
}
// 定义Leader
type Leader struct {
    *Peer
    leaderVariables int
}

How to interpret these two categories?

  1. Peer: It is basically the same as the Peer mentioned above, the difference is that Peer. Leader is empty, it means normal Peer, if it is not empty, it means Leader;
  2. Leader: exactly the same as the Leader mentioned above;

Only this little change will make the logic smoother and the code more elegant. As the object of the service is the Peer type, regardless of whether the identity is switched or the identity judgment becomes natural, as shown in the following code:

// 成功选举为Leader
{
    peer.Leader = &Leader{Peer: peer}
}
// 需要切换身份处理时
{
    if nil != peer.Leader {
        peer.LeaderMethod()
    }
}

If the reader does not have any feeling about the above code and thinks that it is no different from C++/JAVA, either the reader is a great god and doesn't appreciate the author's skill at all, or he hasn't gotten the author's point. Only this little change has refreshed the author's code and logic a lot!

to sum up

In fact, from the perspective of inheritance, there should be no two-way talk, otherwise it is not the concept of inheritance. The author has not borrowed the inheritance mechanism of golang to simplify programming and logic. At this point, we have to mention the essence of inheritance. The following C code is used to describe the essence of inheritance:

// 定义结构体A
struct A {
    int a;
}
// 定义结构体B,并且继承A
struct B {
    struct A a;
    int b;
}

The so-called inheritance is actually that the compiler puts all the member variables of the class in the subclass. When accessing the members of the class (member function or member variable) in the subclass, you can use the dot operator to refer to it, instead of using the C language. You need Baa to access. Of course, the language of the face-to-object has extended many functions in inheritance, which is beyond the scope of this discussion and will not be described too much.

Let's take a look at the inheritance method of golang:

// 定义类型A
type A struct {
    a int
}
// 定义类型B,并且继承A
type B struct {
    A
    b int
}

This inheritance method of golang is the same as C++/JAVA. The new subclass object constructs a class at the same time, because sizeof(B)=sizeof(A)+B member variables are always small (virtual function table is ignored here), The subcategory contains all the contents of the subcategory. But golang has another inheritance method as shown in the following code:

type B struct {
    *A
    b int
}

In this way, new B needs to be new A, which is similar to the previous one. The difference lies in whether the memory is one or two. It is this
difference that gives developers more room to play, and the case mentioned in this article takes advantage of this. Some people may say that this can also be achieved in C language, as shown in the following code:

struct B {
    struct A* a;
    int b;
}

This is true. Although it is a bit cumbersome to refer to members of A, such as Ba->a, the effect achieved by golang is the same. The author often agrees with the ideas of these readers, but what I want to say is: Although the final realization method of two different concepts is the same, each concept has its application place, which can make this concept. The context is easier to understand and clearer. Ba and Ba->a, although the effect is the same, the meaning expressed is different. The meaning of the former a is an attribute of B, and the meaning of the latter a is B and an attribute named a Attributes.

Finally, back to the case of leader and peer. In fact, it is not true two-way inheritance. If the leader inherits from the peer, it is true inheritance.
The leader pointer in the peer should be an attribute of the peer, that is, peer.leader. The author uses golang's inheritance method to
realize the illusion of two-way inheritance.

Guess you like

Origin blog.csdn.net/weixin_42663840/article/details/97695651