Minborg

Minborg
Minborg
Showing posts with label composition. Show all posts
Showing posts with label composition. Show all posts

Wednesday, January 12, 2022

How the Java Language Could Better Support Composition and Delegation

 

How the Java Language Could Better Support Composition and Delegation


This article outlines a way of improving the Java language to better support composition and delegation. Engage in the discussion and contribute to evolving the Java Language.


The Java language lacks explicit semantic support for composition and delegation. This makes delegating classes hard to write, error-prone, hard to read and maintain. For example, delegating a JDBC ResultSet interface entails writing more than 190 delegating methods that essentially provide no additional information, as illustrated at the end of this article, and only add ceremony.


More generally, in the case of composition, Σ m(i) delegating methods need to be written where m(i) is the number of methods for delegate i (provided that all delegate method signatures are disjunct across all the delegates).  


The concept of language support for delegation is not new and there are numerous articles on the subject, including [Bettini08] and [Kabanov11]. Many other programming languages like Kotlin (“Derived”)  and Scala (“export”) have language support for delegation.

In one of my previous articles ”Why General Inheritance is Flawed and How to Finally Fix it”, I described why composition and delegation are so important.


External Tools

Many IDEs have support for generating delegated methods. However, this neither impacts the ability to read nor understand a delegating class. Studies show that code is generally more read than written. There are third-party libraries that provide delegation (e.g. Lombok) but these are non-standard and provide a number of other drawbacks.


More generally, it would be possible to implement a subset of the functionality proposed here in third-party libraries leveraging annotation processors and/or dynamic proxies.


Trends and Industry Standards

As the drawbacks with inheritance were more deeply understood, the trend is to move towards composition instead. With the advent of the module system and generally stricter encapsulation policies, the need for semantic delegation support in the Java language has increased even more.


 I think this is a feature that is best provided within the language itself and not via various third-party libraries. Delegation is a cornerstone of contemporary coding. 


In essence, It should be much easier to “Favor composition over inheritance” as stated in the book “Effective Java” by Joshua Bloch  [Bloch18, Item 18].


Java Record Classes

Many of the problems identified above were also true for data classes before record classes were introduced in Java 14. Upon more thorough analysis, there might be a substantial opportunity to harvest many of the findings made during the development of records and apply these in the field of delegation and composition.


On the Proposal

My intention with this article is not to present a concrete proposal of a way to introduce semantic support for composition and delegation in Java. On the contrary, if this proposal is one of the often 10-15 different discarded initial proposals and sketches on the path that needs to be traversed before a real feature can be proposed in the Java language, it will be a huge success. The way towards semantic support for composition and delegation in Java is likely paved with a number of research papers, several design proposals, incubation, etc. This feature will also compete against other features, potentially deemed to be more important to the Java ecosystem as a whole.


One motto for records was “model data as data” and I think that we should also “model delegation as delegation”.  But what is delegation? There are likely different views on this within the community. 


When I think of delegation, the following springs to mind: A delegating class has the following properties:


  1.     Has one or more delegates

  2.     Delegates methods from its delegates

  3.     Encapsulates its delegates completely

  4.     Implements and/or uses methods from its delegates (arguably)



An Outline - The Emissary

In the following, I will present an outline to tackle the problem. In order to de-bikeshed the discussion, I will introduce a new keyword placeholder called “emissary” which is very unlikely ever to be used in a real implementation. This word could later be replaced by “delegator” or any other descriptive word suitable for the purpose or perhaps even an existing keyword.


 An emissary class has many similarities to a record class and can be used as shown in the example below:


public emissary Bazz(Foo foo, Bar bar);


As can be seen, the Bazz class has two delegates (Foo and Bar) and consequently an equivalent desugared class  is created having two private final fields:


private final Foo foo;

private final Bar bar;


An emissary class is also provided with a constructor. This process could be the same as for records with canonical and compact constructors:


public final class Bazz {


    private final Foo foo;

    private final Bar bar;


    public Bazz(Foo foo, Bar bar) {

       this.foo = foo;

       this.bar = bar;

    }


}


It also makes the emissary class implement Foo and Bar. Because of this, Foo and Bar must be interfaces and not abstract or concrete classes. (In a variant of the current proposal, the implementing interfaces could be explicitly declared).


public final class Bazz implements Foo, Bar {

    private final Foo foo;

    private final Bar bar;


   public Bazz(Foo foo, Bar bar) {

       this.foo = foo;

       this.bar = bar;

   }


}


Now, in order to continue the discussion, we need to describe the example classes Foo and Bar a bit more which is done hereunder:


public interface Foo() {


    void f();


}


public interface Bar() {


    void b();


}


By declaring an emissary class we, unsurprisingly, also get the actual delegation methods so that Bazz will actually implement its interfaces Foo and Bar:


public final class Bazz implements Foo, Bar {


    private final Foo foo;

    private final Bar bar;


    public Bazz(Foo foo, Bar bar) {

        this. Foo = foo;

        this.bar = bar;

    }


    @Override

    void f() {

        foo.f();

    }


    @Override

    void b() {

        bar.b();

    }


}


If the delegates contain methods with the same signature, these would have to be explicitly “de-ambigued”, for example in the same way as default methods in interfaces. Hence, if Foo and Bar both implements c() then Bazz needs to explicitly declare c() to provide reconciliation. One example of this is shown here where both delegates are invoked:


@Override

void c() {

    foo.c();

    bar.c();

}


Nothing prevents us from adding additional methods by hand, for example, to implement additional interfaces the emissary class explicitly implements but that is not covered by any of the delegates.


It is also worth noting that the proposed emissary classes should not get a hashCode(), equals() or toString() methods generated. If they did, they would violate property C and leak information about its delegates. For the same reason, there should be no de-constructor for an emissary class as this bluntly would break encapsulation. Emissary classes should not implement Serializable and the likes by default.


An emissary class, just like a record class, is immutable (or at least unmodifiable and therefore shallowly immutable) and is hence thread-safe if all the delegates are.


Finally, an emissary class would extend java.lang.Emissary, a new proposed abstract class similar to java.lang.Enum and java.lang.Record.


Comparing Record with Emissary

Comparing the existing record and the proposed emissary classes yield some interesting facts:


record


  • Provides a generated hashCode() method

  • Provides a generated equals() method

  • Provides a generated toString() method

  • Provides component getters

  • Cannot declare instance fields other than the private final fields which correspond to components of the state description


emissary


  • Does not provide a generated hashCode() method

  • Does not provide a generated equals() method

  • Does not provide a generated  toString() method

  • Provides delegating methods

  • Implements delegates (in one variant)

  • Can declare additional final instance fields other than the private final fields which correspond to delegates


both 


  • A private final field for each component/delegate of the state description

  • A public constructor, whose signature is the same as the state/delegate description, that initializes each field from the corresponding argument; (canonical constructor and compact constructor)

  • Gives up the ability to decouple API from representation

  • Implicitly final, and cannot be abstract (ensuring immutability)

  • Cannot extend any other class (ensures immutability)

  • Extends a java.lang class other than Object.

  • Can declare additional methods not covered by the properties/delegates


Anticipated Use Cases


Here are some use cases of the emissary class:


Composition


Providing an Implementation for one or several interfaces using composition:


  public emissary FooAndBar(Foo foo, Bar bar);


Encapsulation


Encapsulating an existing instance of a class, hiding the details of the actual implementation:


  private emissary EncapsulatedResultSet(ResultSet resultSet);


  …


  ResultSet rs = stmt.executeQuery(query);


  return new EncapsulatedResultSet(rs);


Disallow down-casting


Disallow the down-casting of an instance. I.e. an emissary class implements a restricted sub-set of its delegate’s methods where the non-exposed methods cannot be invoked via casting or reflection. 


String implements CharSequence and in the example below, we provide a String viewed as a CharSequence whereby we cannot down-cast the CharSequence wrapper back to a String


  private emissary AsCharSequence(CharSequence s);


  return new AsCharSequence(“I am a bit incognito.”);


Services and Components


Providing an implementation of an interface that has an internal implementation. The internal component package is typically not exported in the module-info file:


  public emissary MyComponent(MyComponent comp) {


      public MyComponent() {

          this(new InternalMyComponentImpl());

      }


      // Optionally, we may want to hide the public 

      // constructor

      private MyComponent(MyComponent comp) {

         this.comp = comp;

      } 


  }


  MyComponent myComp = ServiceLoader.load(MyComponent.class)

                           .iterator()

                           .next();


Note: If InternalMyComponentImpl is composed of an internal base class, contains annotations, has non-public methods, has fields etc. These will be completely hidden from direct discovery via reflection by the emissary class and under JPMS, it will be completely protected from deep reflection. 


Comparing Two ResultSet Delegators


Comparison between two classes delegating a ResultSet:


Emissary Class


// Using an emissary class. A one-liner

public emissary EncapsulatedResultSet(ResultSet resultSet);


IDE Generation


// Using automatic IDE delegation. About 1,000 lines!

public final class EncapsulatedResultSet implements ResultSet {


    private final ResultSet delegate;


    public EncapsulatedResultSet(ResultSet delegate) {

        this.delegate = delegate;

    }


    @Override

    public boolean next() throws SQLException {

        return delegate.next();

    }


  // About 1000 additional lines are not shown here for brevity…



Conclusions


We may conceptually reuse record classes for providing semantic composition and delegation support in the Java language. This would greatly reduce the language ceremony needed for these kinds of constructs and would very likely nudge developers towards using composition just like record classes nudged developers towards immutability. 


The scientific field of composition and delegation and what is related to is much bigger than indicated in this article. Further studies are needed before arriving at a concrete proposal. Perhaps this is just a part of something bigger?


Language support for composition and delegation in some form would make Java an even better language in my opinion.


References

[Bettini08]

Bettini, Lorenzo. “Typesafe dynamic object delegation in class-based languages”, PPPJ '08: Proceedings of the 6th international symposium on Principles and practice of programming in Java, September 2008, Pages 171–180, https://doi.org/10.1145/1411732.1411756


[Kabanov11]

Kabanov, Jevgeni. “On designing safe and flexible embedded DSLs with Java 5”, Science of Computer Programming, Volume 76, Issue 11, November 2011 pp 970–991, https://doi.org/10.1016/j.scico.2010.04.005


[Bloch18]

Bloch, Joshua., Effective Java, Third Edition, ISBN 0-13-468599-7, 2018




Wednesday, December 8, 2021

Why General Inheritance is Flawed and How to Finally Fix it

 

Why General Inheritance is Flawed and How to Finally Fix it


By leveraging composition and the final keyword in the right way, you can improve your programming skills and become a better Java programmer.  


General inheritance, whereby a public class is extended over package boundaries, provides a number of challenges and drawbacks and should be avoided in almost all cases. Classes and methods can be made final meaning that subclassing is disallowed which effectively prevents inheritance. While this may sound like a strange thing to do in an object-oriented language like Java, it does carry significant benefits for a large number of class types.


But, when should a class or method be final and just why is general inheritance problematic?

Immutable Classes

Immutable classes are classes whose state can not be observed to change from the outside world. This gives immutable objects the advantage of being inherently thread-safe and they can be reused indefinitely.


Java’s built-in String class is an example of an immutable class. It does have an internal state that is very likely to change the first time hashCode() is called, but this internal state cannot be observed by an outside caller (unless resorting to reflection).


Immutable classes shall always be declared final or else subclasses could compromise the immutability contract, simply by adding and exposing a mutable state. 


For the sake of completeness, it is worth mentioning that an immutable class should declare all its fields as private, final and ensure exclusive access to any mutable sub-component (such as an array), for example using defensive copying.

Non-instantiable Classes (aka Utility Classes)

A non-instantiable class is often informally referred to as a “utility class” and contains only static methods (and perhaps static fields). Static methods are not class methods but rather global functions attached to a “carrier-class”.  Ideally, non-instantiable classes should be immutable concerning their (static) state (if any).


These methods should be called using their carrier-class name followed by the method name (e.g. Collections.emptyList()). Subclassing a non-instantiable utility can result in non-intuitive behavior and is likely a source of confusion as the methods cannot be overridden anyhow, only replaced as illustrated hereunder:


public class FooUtil {


    static void print() {

        lower();

    }


    static void lower() {

        System.out.println("lower foo");

    }


}



public class BarUtil extends FooUtil {


    static void lower() {

        System.out.println("lower bar");

    }


}


Invoking BarUtil::print will produce “lower foo” and not “lower bar” meaning that BarUtil::lower did not override FooUtil::lower. However, if BarUtil::lower was called directly, it would have printed “lower bar”.


Therefore, non-instantiable classes should generally be declared final


As a side note, non-instantiable classes should have a single default constructor declared private to prevent instantiation of the non-instantiable class (as the name implies).

Methods Called by a Constructor

Methods called by a constructor of a class should always be final, either by declaring the entire class final or by declaring these methods final. Failure to do this may open up a leak of an object (e.g. “this”) that is only partially initialized and thus is likely in an illegal state. Such a leak may, for example, occur by the not-yet-initialized instance registering itself with a listener. These errors are likely hard to identify if they make it out in the open.

General Inheritance

The use/non-use of general inheritance has sparked opinionated discussions for quite some time.


Back in the early days, inheritance was often thought to be the general way of code reuse. As it later turned out, inheritance outside a package could lead to unsatisfiable and erroneous behaviour unless special care is put into providing classes that are suitable to extend across package boundaries [Bloch18, Item18]. 


Furthermore, general inheritance breaks encapsulation [Snyder80] because the superclass implementation might change over time which might cause a subclass to fail even though no changes were made. This problem might be avoided if one commits to never change the superclass, effectively making the superclass a large monolithic fossil API commitment for eternal times. In all fairness, this argument can also be raised against classes using composition even though there are fewer ways problems can leak into the code. So, this is not an argument for finalization but rather a more fundamental problem with code reuse.


Inheritance could produce unintended effects due to self-use, whereby an overridable method calls another overridable method in the base class: Imagine a class that extends ArrayList and that is supposed to keep track of the number of elements ever added to the class. If we override add() bumping the counter by one and override addAll(Collection) adding Collection.size() to the counter after which the corresponding super method is called,  then we are in for a surprise: 


Because ArrayList::addAll happens to self-use ArrayList::add to individually add the elements, additions via addAll() will count twice. Furthermore, there is no guarantee that this behavior will stay the same over time unless it is documented. Maybe there will be a more performant way of bulk-adding elements in the future whereby elements are directly inserted in the backing array without calling add()?


Another common problem with self-use is when a subclass overrides a method that is supposed to call one or several other methods but the programmer forgets to call the super method. A related problem is the problem of deciding if an overriding method should call the super method at the beginning or at the end of the overridden method (or indeed somewhere in between).  A solution to some of these problems could be to declare the top method final in the base class and provide overridable protected “hook methods” that can be overridden in a more controlled fashion. 


General inheritance also opens up potential security vulnerabilities: Suppose an ArrayList was extended to ensure only objects fulfilling a certain predicate could be added (e.g. they must be in a valid state). Then, in a later release, a new way of adding elements was introduced via the base class AbstractList. This new way will now become visible in the supposedly safeguarded class, effectively providing a back-door for adding illegal objects to the list. 


Another problem is “propagating exposure” as exemplified by  Arrays.asList(“a”, “b”) which returns a “fixed-size list” (but ought to return an unmodifiable List and here an immutable List as the elements themselves are all immutable). As it turns out, elements in the returned List may now not only be replaced via an Iterator but also via the List::replaceAll,a  method added in JDK 8 after the inception of Arrays::asList.


An additional class of problems might arise if a subclass adds a new method to the ones of the base class. If at a later stage, a method with the same signature is added to the base class, then this method will be coincidentally overridden by the subclass. This is likely not the intended behavior at all. If a method with the same name and parameters are added but with a different return type, then the code will likely fail to compile. So in the general case, it is not possible to ever add methods in a non-final public class as there is no control of how the class is subclassed.


Yet another problem could be incidental inheritance. The JDK itself has several problematic inheritances whereby classes were incidentally inherited because it was apparently “convenient“ and not because class B indeed was class A. For example, Stack extends the old Vector class for no good principal reason. This prevents Stack from evolving to a more efficient and performant implementation.


To summarize, a class that is supposed to be generally inherited is very hard to ever change and must [Bloch18, Item19]:

  • Document its self-use of overridable methods

  • Potentially providing hooks in the form of judiciously chosen protective methods

  • Be accompanied by tests using subclasses

  • Not provide a constructor that invokes overridable methods

  • Not allow serialization to invoke overridable methods



Inheriting also creates constraints and problems if hashCode()/equals() are overridden. If we have a base class called Fruit, then is an Apple with the same color as a Pear equal? Can an instance of SevilleOrange ever be equal to a BergamontOrange instance?  Generally, it is not easy to decide these kinds of questions. It is important to remember that any subclass should either override none of these methods or should override them both.


It should be noted that exposing a public non-final class in a public API by definition means that it opens up for inheritance across package boundaries as user-land code can place extending classes in any packet. Since split packages are strongly discouraged or might even be entirely forbidden depending on the use of JPMS, subclassing such a class implies subclassing over package boundaries.


One way of avoiding all these things is to declare classes final and use composition instead of inheritance, effectively abandoning inheritance across packages. This often provides a much cleaner API whereby only interfaces can be exposed and concrete classes do not leak out in the API. This way, any superclass used is only package-private and can, by convention or definition, never be used externally.


Composition with delegation protects against most of the problems mentioned above including unintended self-use, security holes via extra methods in base classes, signature collisions,  incidental inheritance, need of subclass testing, accidental leak of “this” and many other problems. In the past, it was feared that this would lead to reduced performance but this is simply not the case.


Inheritance in Java is, for good reasons, restricted to one superclass which naturally limits the scalability of the concept. Composition, on the other hand, allows an arbitrary number of delegates to be used.


A small drawback with composition could materialize in combination with the use of certain callbacks. However, this problem can be avoided if proper provisions are put in. In other words, if a component (used in composition) registers itself with a listener, then the listener will invoke the component itself and not the composing class.

Sealed Classes

In more recent Java versions, the concept of sealed classes (JEP 409) was introduced. Before this, the final keyword was a boolean property: either a class was extensible (within its declared access type) or it was not. Sealed classes introduce a more granular mechanism whereby it can be said that a Fruit can either be an Apple, Pear or Orange but nothing more. This is fundamentally a more generalized form of final. The amount of effort put into the Java languages with features like this indicates a class extensibility is an important property. Interestingly, a permitted class in a sealed interface must specify whether itself is final, non-final or permits subsequent subclasses. 


API Commitments Imposed by Inheritance

In this article, the class Stack was mentioned as a failed inheritance implementation. It basically introduces the methods push(), pop(), peek(), empty() and search(). But, as it inherits from Vector, we also get all the methods/classes from List, AbstractList, RandomAccess, Cloneable and SerializableAbstractList, which in turn, inherits from AbstractCollection which implements Collection.


This increases the API weight by orders of magnitudes and I am perfectly certain the Java designers are regretting their incidental inheritance 25 years down the line. If Stack was just an interface and there was a static method available that provided a new empty Stack, things would look much better.


Classes that are Serializable or subject to other serialization mechanisms are often particularly problematic as the binary (or other) format more often than not limits the way implementations can ever evolve over time.


As seen above and in previous clauses, a public non-final class cannot ever change in many cases.

Should Inheritance Across Package Boundaries Ever be Used?

This is a matter of opinion. 


Many times, it is better to use composition. In simpler cases delivering functions to a concrete class’ constructor providing tailored functionality would be preferable over allowing subclassing and overriding methods. To give an example of this, instead of overriding a handler method, a method handler could be provided via the constructor to a non-extensible class.


If, after very careful consideration, one arrives at the conclusion that one should provide an extensible class (across packages), then all the constraints above must be taken into careful consideration. Just allowing subclassing by default is a right-out mistake, particularly for library and API designers. Instead, classes should be marked final by default, and only after careful review and testing, opening up for subclassing could be regarded.

A Final Note

As I moved away from using inheritance across packages and switched to exposing just interfaces, many other advantages became apparent. It becomes much easier to keep internal considerations… well internal.


Composition whereby potentially several components can be used in a single class provides more code reuse capability than inheritance, albeit requiring a bit more code ceremony in the using class. It can also simplify testing of the code and provides better test coverage with much fewer and less brittle tests.


It also fits very well with the module system (JPMS).  Providing components as pure services, for example, using Java’s ServiceLoader, adds flexibility while minimizing the API footprint. This makes it easier to learn and use the API and provides much more flexibility to evolve libraries over time. 


Finally, it all makes sense...

References

[Bloch18]

Bloch, Joshua., Effective Java, Third Edition, ISBN 0-13-468599-7, 2018


[Snyder80]

Snyder, Allan. “Encapsulation and Inheritance in Object-Oriented Programming Languages”. In Object-Oriented Programming Systems, Language and Applications Proceedings, 35-45, New-York, NY ACM Press.