Sunday, March 24, 2019

Mixin, the Diamond problem, inheritance (English)


The inheritance mechanism is a core component of the Object Oriented Programming paradigm. Inheritance means that a class "inherits" another class's definition and type. Inheritance express IS-A relationship. A dog IS-A pet, a car IS-A vehicle.


I want to emphases it again: code-reuse is not a goal for inheritance. Code-reuse is a goal of mixin (see below). In Python mixin implemented by using of (cooperative multiple) inheritance (see below).

There is more below.
Ниже есть продолжение.



The "diamond problem" is an ambiguity that arises when two classes B and C inherit from A, and class D inherits from both B and C. If there is a method in A that B and C have overridden, and D does not override it, then which version of the method does D inherit: that of B, or that of C?



https://en.wikipedia.org/wiki/Multiple_inheritance#The_diamond_problem

It is called the "diamond problem" because of the shape of the class inheritance diagram in this situation. In this case, class A is at the top, both B and C separately beneath it, and D joins the two together at the bottom to form a diamond shape.

Mixin Wikipedia defines a mixin as "a class that provides a certain functionality to be inherited by a subclass, but is not meant to stand alone. Inheriting from a mixin is not a form of specialization but is rather a means to collect functionality. A subclass may even choose to inherit most or all of its functionality by inheriting from one or more mixins through multiple inheritance. A mixin can also be viewed as an interface with implemented methods."

Inheritance defines an "is a" relationship between classes. A dog is 'is a' pet, a car "is a" vehicle — a car is a specialization of a vehicle. A mixin, on the otherhand, is a means of sharing functionality among a series of related classes that do not inherit from the same base class. A mixin allows you to reuse code without implying a relationship among the classes. It also allows you to get around the Single Inheritance model a bit to do this.

I'm starting with JavaScript example, because its implement mixin by actually copying methods from mixin to object, that is good mental model for that mixin is.

All other examples (with exception of C#) use inheritance to implement mixin that is theoretically wrong, because mixin don't have IS-A semantic.


Mixin in JavaScript
Based on https://en.wikipedia.org/wiki/Mixin#In_JavaScript

It is technically possible to add behavior to an object by binding functions to keys in the object. However, this lack of separation between state and behavior.

First way - using extend(obj, mixin) function.

'use strict';

const Halfling = function (fName, lName) {
  this.firstName = fName;
  this.lastName = lName;
};

const mixin = {
  fullName() {
    return this.firstName + ' ' + this.lastName;
  },
  rename(first, last) {
    this.firstName = first;
    this.lastName = last;
    return this;
  }
};

// An extend function
const extend = (obj, mixin) => {
  Object.keys(mixin).forEach(key => obj[key] = mixin[key]);
  return obj;
};

const sam = new Halfling('Sam', 'Loawry');
const frodo = new Halfling('Freeda', 'Baggs');

// Mixin the other methods
extend(Halfling.prototype, mixin);

console.log(sam.fullName());  // Sam Loawry
console.log(frodo.fullName());  // Freeda Baggs

sam.rename('Samwise', 'Gamgee');
frodo.rename('Frodo', 'Baggins');

console.log(sam.fullName());  // Samwise Gamgee
console.log(frodo.fullName());  // Frodo Baggins

Second way - Object.assign(obj, mixin)

'use strict';

// Creating an object
const obj1 = {
  name: 'Marcus Aurelius',
  city: 'Rome',
  born: '121-04-26'
};

// Mixin 1
const mix1 = {
  toString() {
    return `${this.name} was born in ${this.city} in ${this.born}`;
  },
  age() {
    const year = new Date().getFullYear();
    const born = new Date(this.born).getFullYear();
    return year - born;
  }
};
// Mixin 2
const mix2 = {
  toString() {
    return `${this.name} - ${this.city} - ${this.born}`;
  }
};

//  Adding the methods from mixins to the object using Object.assign()
Object.assign(obj1, mix1, mix2);

console.log(obj1.toString());   // Marcus Aurelius - Rome - 121-04-26
console.log(`His age is ${obj1.age()} as of today`);  // His age is 1897 as of today


Java with version below 8 Pre Java-8 Java Object's doesn't have multiple inheritance. The standard way to simulate multiple inheritance in Java is delegation / composition / decorator.

Delegation:

class A {
  private B b = new B();
  private C c = new C();

  public void method1() {
    b.methodB();
  }


  public void method2() {
    b.methodB();
  }
}

public class Driver {
  public static void main(String[] args){
    A a=new A();
    a.method1();
    a.method2();
  }
}

Decorator:
class A {
  private B b;
  private C c;

  public A(B b, C c){
    this.b=b;
    this.c=c;
  }

  public void method1() {
    b.methodB();
  }


  public void method2() {
    b.methodB();
  }
}

public class Driver {
  public static void main(String[] args){
    B b=new B();
    C c=new C();
    A a=new A(b, c);

    a.method1();
    a.method2();
  }
}

The ultimate example of such technique is BufferedOutputStream. Ability to buffer stream modeled in Java as Object (because "'everything' in Java is object"). Actually, it should be mixin. This is orthogonal concept. For example, this ability is also used in BufferedInputStream.

Note: Pre Java-8 has multiple inheritance with interfaces. Class can implements multiple interfaces (interface can also extends another interfaces). The ambiguity that raise with method name collision is very limited. Anyway, the collision is between two (or more) purely abstract method. Provided, that interface has no state, it doesn't really matter which method wins (in order to the class to be usable, you should provided implemetaion of such method in the class anyway, and such implementation will be used for all purely abstract methods). In particular, if we have diamond problem, the problem is with purely abstract methods, so the issue is very limited as described above.

Java in version 8 and above.
Java 8 interface has limited support for multiple inheritance in interfaces. You can have actual behavior there (by default (non-abstract) methods; also you can hive static methods, but this has nothing to do with inheritance), but you can't have state there (no data-members allowed). So, while technically you have diamond problem, the method resolution rules was specifically crafted in such a way that there is no ambiguate on which class to call. State appears only when you have class that implemented these interfaces. If he provides his own implementation, it's implementation wins, if didn't, implementation of one the interfaces will be used (Java doesn't take in consideration the order of the specified interfaces in the implements clause), but since interface don't have the state, it doesn't matter which particular interface will be used.

Note: Java 8 interfaces are still stateless.

With the introduction of default methods in Java 8, it is now possible for a class to inherit the same method from multiple places (such as another class or interface). The following rules can be used to determine which method is selected in such cases:

1. A class or superclass method declaration always takes priority over a default method
2. Otherwise, the method with the most specific default-providing interface is used
3. Finally, if the methods are equally specific, there will be a compiler error and you will be forced to explicitly override the method and specify which one your class should call
...

public interface A {
  default void name() {
    System.out.println("A");
  }
}

public interface B extends A {}

public interface C extends A {}

public class D implements B, C {
  public static void main(final String... args) {
    new D().name();
  }
}

Answer: A

The sub-interfaces B and C haven't overridden the method, so there is actually only the method from A to choose from. As a side note, if either B or C (but not both) had overridden the method, then Rule 2 would have applied. By the way, this is the diamond problem.

https://fahdshariff.blogspot.com/2016/06/java-8-default-method-resolution-rules.html

If rule 1 and rule 2 fail to determine which method to use, than by rule 3 we should add method in the class that implements there interfaces, that will provide explicit call to one of the interfaces. See the link above for the detail explanation and examples of these rules.



Scala
Based on https://en.wikipedia.org/wiki/Mixin#In_JavaScript

...As their name reveals, Traits are usually used to represent a distinct feature or aspect that is normally orthogonal to the responsibility of a concrete type or at least of a certain instance. For example, the ability to sing is modeled as such an orthogonal feature: it could be applied to Birds, Persons, etc.

Note: Scala's trait can have state. Scala support multiple inheritance of traits. There are stricked rule on how mulptile inheritance is resolved. Java 8 interfaces are trait--.

trait Singer{
  def sing { println(" singing ...") }
  //more methods
}

class Birds extends Singer


Here, Bird has mixed in all methods of the trait into its own definition as if class Bird had defined method sing() on its own.

As extends is also used to inherit from a super class, in case of a trait extends is used if no super class is inherited and only for mixin in the first trait. All following traits are mixed in using keyword with.

trait Performer{
  def perform{ println(" performing... ") }
  //more methods
}


class Person
class Actor extends Person with Singer
class Actor extends Singer with Performer

C# 3.0 can also simulate mixin by using extension method, see Implementing Mixins with C# Extension Methods.

Kotlin can simulate mixin in both way, as Java 8 interfaces and as extension methods.

Python
Based on https://en.wikipedia.org/wiki/Mixin#In_Python

In Python mixin implemented by using of inheritance.

In Python, the SocketServer module has both a UDPServer class and a TCPServer class. They act as servers for UDP and TCP socket servers, respectively. Additionally, there are two mixin classes: ForkingMixIn and ThreadingMixIn. Normally, all new connections are handled within the same process. By extending TCPServer with the ThreadingMixIn as follows:

class ThreadingTCPServer(ThreadingMixIn, TCPServer):
  pass

the ThreadingMixIn class adds functionality to the TCP server such that each new connection creates a new thread. Alternatively, using the ForkingMixIn would cause the process to be forked for each new connection. Clearly, the functionality to create a new thread or fork a process is not terribly useful as a stand-alone class.

In this usage example, the mixins provide alternative underlying functionality without affecting the functionality as a socket server.




No comments:

Post a Comment