Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does fragile base class issue exist in Go?

Despite using composition over inheritance?

If so, is there any solution for it at the language level?

like image 314
Mahdi Avatar asked Jan 24 '17 02:01

Mahdi


People also ask

Why are there no classes in go?

Such thing does not happen with Go, because Go has functions. Not everything needs to be an object, or a method, in the real world. “Classes” and “objects” are very useful but they cannot be used for everything.

Why does Golang not have inheritance?

Since Golang does not support classes, so inheritance takes place through struct embedding. We cannot directly extend structs but rather use a concept called composition where the struct is used to form other objects. So, you can say there is No Inheritance Concept in Golang.

What is fragile base class in Java?

The fragile base class problem is a fundamental architectural problem of object-oriented programming systems where base classes (superclasses) are considered "fragile" because seemingly safe modifications to a base class, when inherited by the derived classes, may cause the derived classes to malfunction.


2 Answers

As VonC wrote, but I'd like to point out something.

The fragile base class problem is often blamed on virtual methods (dynamic dispatch of methods – this means if methods can be overridden, the actual implementation that has to be called in case of such an overridden method can only be decided at runtime).

Why is this a problem? You have a class, you add some methods to it, and if MethodA() calls MethodB(), you can't have any guarantee that the MethodB() you wrote will be called and not some other method of a subclass that overrides your MethodB().

In Go there is embedding, but there is no polymorphism. If you embed a type in a struct, all the methods of the embedded type get promoted and will be in the method set of the wrapper struct type. But you can't "override" the promoted methods. Sure, you can add your own method with the same name, and calling a method by that name on the wrapper struct will invoke your method, but if this method is called from the embedded type, that will not be dispatched to your method, it will still call the "original" method that was defined to the embedded type.

So because of this, I'd say the fragile base class problem is only present in a quite mitigated form in Go.

Example

Demonstrating the problem in Java

Let's see an example. First in Java, because Java "suffers" from this kind of problem. Let's create a simple Counter class and a MyCounter subclass:

class Counter {
    int value;

    void inc() {
        value++;
    }

    void incBy(int n) {
        value += n;
    }
}

class MyCounter extends Counter {
    void inc() {
        incBy(1);
    }
}

Instantiating and using MyCounter:

MyCounter m = new MyCounter();
m.inc();
System.out.println(m.value);
m.incBy(2);
System.out.println(m.value);

The output is as expected:

1
3

So far so good. Now if the base class, Counter.incBy() would be changed to this:

void incBy(int n) {
    for (; n > 0; n--) {
        inc();
    }
}

The base class Counter still remains flawless and operational. But the MyCounter becomes malfunctioning: MyCounter.inc() calls Counter.incBy(), which calls inc() but due to dynamic dispatch, it will call MyCounter.inc()... yes... endless loop. Stack overflow error.

Demonstrating the lack of the problem in Go

Now let's see the same example, this time written in Go:

type Counter struct {
    value int
}

func (c *Counter) Inc() {
    c.value++
}

func (c *Counter) IncBy(n int) {
    c.value += n
}

type MyCounter struct {
    Counter
}

func (m *MyCounter) Inc() {
    m.IncBy(1)
}

Testing it:

m := &MyCounter{}
m.Inc()
fmt.Println(m.value)
m.IncBy(2)
fmt.Println(m.value)

Output is as expected (try it on the Go Playground):

1
3

Now let's change Counter.Inc() the same way we did in the Java example:

func (c *Counter) IncBy(n int) {
    for ; n > 0; n-- {
        c.Inc()
    }
}

It runs perfectly, the output is the same. Try it on the Go Playground.

What happens here is that MyCounter.Inc() will call Counter.IncBy() which will call Inc(), but this Inc() will be Counter.Inc(), so no endless loop here. Counter doesn't even know about MyCounter, it does not have any reference to the embedder MyCounter value.

like image 108
icza Avatar answered Oct 19 '22 23:10

icza


The Fragile base class problem is when a seemingly safe modifications to a base class, when inherited by the derived classes, may cause the derived classes to malfunction.

As mentioned in this tutorial:

For all intents and purposes, composition by embedding an anonymous type is equivalent to implementation inheritance. An embedded struct is just as fragile as a base class.

like image 45
VonC Avatar answered Oct 19 '22 22:10

VonC