Composition in EK9

Composition has been covered in the traits section in simple form. This section will discuss composition in more detail through an example. You may be thinking why labour this traits and composition stuff so much?

See the rationale and logic below as to why there is so much focus on this.

Composition background

In general when Object Oriented programming first started to gain ground, the movement of defining data structures ('structs in C') and having functions manipulate those 'structs' fell out of use.

This was generally considered a good thing by Object Oriented developers, now classes and class hierarchies became the main driving force. Initially the classes developed were relatively simple to understand. However it was not long before very large and deep class hierarchies became common. In many cases with multiple inheritance adding to the complexity.

With EK9 records and functions you can now use the old approach again (and it's not a bad practice - it's a valid and powerful approach if wish to adopt a functional development style).

Add into the mix the teaching of Object Oriented programmers to model 'real life' concepts and the rot set in! Yes a Square 'is a' type of Rectangle mathematically, but that does not mean you can or should model it like that in a class structure.

Now throw in the convenience of just wanting some of the same functionality from another class and gaining that functionality through inheritance - and we have massive interdependence and coupling. By providing protected fields/properties; even 'encapsulation' is lost.

With Java classes, things became slightly simpler because it did not support multiple inheritance and Java Interfaces only had abstract methods (originally). But again as inheritance was really the main mechanism developers focused on to augment and add functionality; hierarchies became quite deep.

You may consider the above statements incorrect, opinionated, invalid or extreme. These are opinions, but the EK9 language has been driven in part by experience of using 'C', Structs - going through the joy and pain of Object Oriented programming.

So why isn't composition more widely used/promoted? It's probably because it has not been made easy enough. Imagine a trait/interface with 20 methods on, implement a class and provide implementations for all 20 methods. Now imagine wanting to compose that class with another that implements the same trait/interface. You have to write 20 methods that just call delegate.{methodName}!

This is what EK9 does for you with the by ... syntax (see later example for details). What this means is that a developer can now create a class that has multiple traits but each of those traits (and all the methods) can be delegated to existing concrete classes that have already been developed. In addition if you do what to override a small selection of the methods; you can and EK9 won't delegate those.

Smaller and well defined functionality

In general the smaller, well defined and immutable a piece functionality is; the:

The SOLID principle can be observed and can be followed through the use of composition more easily than just using inheritance.

Functions

A function is probably the smallest and most reusable piece of functionality that could be developed, but in in general has no state of its own. With the advent of Object Oriented programming this very useful and reliable mechanism of development was discarded to some degree. Not so much with C++, but with Java to some extent.

With EK9 the function is considered a critical, valuable, reliable, robust and scalable software construct. By employing function delegates; a mechanism of adjusting functionality of classes without sub-classing (inheritance) can be realised. This is shown in this example (see Square).

The only disadvantage of using function delegates like this, is that the function does not have direct access to the state of the object. Some developers would see this as an advantage rather than a disadvantage! It respects encapsulated data and state.

Small Classes

There is a tendency for developers to create God classes. These have way too much functionality or manipulate other objects (as if they were C structs; though they go through the motions of Object Orientation by just using 'getters'/'setters'). In general they tend to be very large, link to many other classes or have very deep hierarchies.

Breaking these large classes up into much smaller classes, functions and records and then composing them together behind a class that delegates the call through to each of the smaller classes is a step in the right direction.

By creating smaller well rounded and encapsulated classes; defects and complexity are reduced and reuse is increased. They can more easily follow the single responsibility principle.

Façade

The approach of breaking up a large class in to several smaller classes may still require some sort of unifying 'interface' to some degree (take care here not to build a very large 'interface'). In most modern languages an interface type would be defined with all (or most) the same methods on the classes re-declared. Furthermore the class that implements the interface must redeclare/implement those methods yet again and must also the call the method on the actual class it will use to provide that functionality.

EK9 has a solution to the above through the use of traits as shown in the example below, specifically the 'Employee' using traits by delegating to a matching Property class.

Inheritance

There is a section on inheritance that details this approach. Use of inheritance is still a key technique and useful mechanism to represent data structures. Inheritance to model concepts that are naturally structured, rather than inheritance just because there is similarity in data elements or processing is the most common approach to using inheritance.

As a short example; consider a class called ExternalAspect that has properties of colour and texture. Human, Bird and Chair all share those properties to some degree. But they are not 'an' ExternalAspect! They could have a property of ExternalAspect however. With real world examples this is can sometimes be quite obvious, but with more abstract software concepts this become blurred or confused at times.

But even with real world examples, it is not always appropriate to make class structures and inheritance follow those concepts too closely. This is sometimes a major source of confusion as there can be a conflict in true reality and what you need to model in software.

Contradictions

There are now some contradictions that must now be addressed.

EK9 Approach

We need the benefits of being able to create small well defined classes and the benefits of the Façade. But we don't want the disadvantages of either (again don't make the Façade too large).

Function delegates provide the first part of the solution, the second part is the use of traits. The trait provides a simple well defined public interface that can support default functionality and abstract method signatures. The third part of the solution is to enable multiple inheritance of traits so that Façades can be defined just by creating a new trait that has traits of all the features needed. The fourth and final part of the solution is to incorporate class/object delegation when defining a class; this reduces the repetition and unnecessary need to call each and every method that requires delegation. See the example below, specifically:

Employee with trait of IRole by role, IPayrollCalculator by payroll

Example

The use of composition is really a design pattern that can be used with any language. The diagrams below are from a Python example, which was originally taken from a book on C++ design. The site is quite long (but well detailed and interesting). The main points of contrast are shown in two diagrams, one for inheritance and one for composition.

The design has remained fairly unaltered in the EK9 implementation, just a few alterations here and there. While the final design might not be perfect or what some OO designers would like, it does provide a good example of the issues and proposes a solution that could be used or refined and improved further.

Inheritance
Inheritance

As you can see the inheritance design is quite complex and has multiple inheritance.

Composition
Composition

The composition design is simpler and focuses on composition around the employee. It might have been more appropriate to model 'Employment', 'Employee' and 'Person' rather than just use 'Employee'; but that is a modelling discussion rather than the focus of this; which is composition.

Composition as above in EK9

The following example is quite long, this is to ensure that the benefits of the EK9 approach to composition are made obvious.

The same names for the constructs have been used in the EK9 code example below. This makes the mapping between the composition diagram above and the EK9 code obvious. Method names have been altered and there is a slight change in the inheritance structure (which is more logical). In all; it is about 250 lines of code. The implementation is broadly the same as the Python code, There are several alternatives to the implementation in EK9 (such as using text constructs or components). These have not been done to keep the example a similar to the Python and the point of using composition.

Composition Example

#!ek9
defines module introduction

  defines record

    Address
      street as String: String()
      street2 as String: String()
      city as String: String()
      state as String: String()
      zipcode as String: String()

      operator ?
        <- rtn as Boolean: street? and city? and state? and zipcode?
      operator $
        <- rtn as String: ""

        rtn += street
        if street2?
          rtn += "\n" + street2
        rtn += "\n" + city + " " + state+ " " + zipcode

  defines trait

    IRole
      performDuties()
        -> hours as Integer
        <- activity as String

    IPayrollCalculator
      trackWork()
        -> hours as Integer
      calculatePayroll()
        <- amount as Money

  defines class

    Employee with trait of IRole by role, IPayrollCalculator by payroll
      id as Integer: Integer()
      name as String: String()
      address as Address: Address()
      role as IRole?
      payroll as IPayrollCalculator?

      Employee()
        ->
          id as Integer
          name as String
          address as Address
          role as IRole
          payroll as IPayrollCalculator

        this.id: id
        this.name: name
        this.address: address
        this.role: role
        this.payroll: payroll

      work()
        -> hours as Integer

        stdout <- Stdout()
        duties <- performDuties(hours)
        stdout.println("Employee " + $this + ": ")
        stdout.println(" - " + duties)
        stdout.println("")
        payroll.trackWork(hours)

      address()
        <- rtn as Address: address

      operator $
        <- rtn as String: $id + " - " + name

    AddressBook
      employeeAddresses as Dict of (Integer, Address): {
        1: Address("121 Admin Rd.", String(), "Concord", "NH", "03301"),
        2: Address("67 Paperwork Ave", String(), "Manchester", "NH", "03101"),
        3: Address("15 Rose St", "Apt. B-1", "Concord", "NH", "03301"),
        4: Address("39 Sole St.", String(), "Concord", "NH", "03301"),
        5: Address("99 Mountain Rd.", String(), "Concord", "NH", "03301")
        }

      getEmployeeAddress()
        -> employeeId as Integer
        <- rtn as Address: Address()

        address <- employeeAddresses.get(employeeId)
        assert address?
        rtn: address.get()

    ProductivitySystem
      roles as Dict of (String, IRole): {
        "manager": ManagerRole(),
        "secretary": SecretaryRole(),
        "sales": SalesRole(),
        "factory": FactoryRole()
        }

      getRole()
        -> roleId as String
        <- rtn as IRole

        theRole <- roles.get(roleId)
        assert theRole?
        rtn: theRole.get()

      track()
        ->
          employees as List of Employee
          hours as Integer

        stdout <- Stdout()
        stdout.println("Tracking Employee Productivity")
        stdout.println("==============================")
        for employee in employees
          employee.work(hours)
        stdout.println("")

    ManagerRole with trait of IRole
      override performDuties()
        -> hours as Integer
        <- activity as String: "screams and yells for " + $hours + " hours."

    SecretaryRole with trait of IRole
      override performDuties()
        -> hours as Integer
        <- activity as String: "does paperwork for " + $hours + " hours."

    SalesRole with trait of IRole
      override performDuties()
        -> hours as Integer
        <- activity as String: "expends " + $hours + " hours on the phone."

    FactoryRole with trait of IRole
      override performDuties()
        -> hours as Integer
        <- activity as String: "manufactures gadgets for " + $hours + " hours."

    PayrollSystem
      employeePolicies as Dict of (Integer, PayrollPolicy): {
        1: SalaryPolicy(3000#USD),
        2: SalaryPolicy(1500#USD),
        3: CommissionPolicy(1000#USD, 100#USD),
        4: HourlyPolicy(15#USD),
        5: HourlyPolicy(9#USD)
        }

      getPolicy()
        -> employeeId as Integer
        <- rtn as PayrollPolicy

        policy <- employeePolicies.get(employeeId)
        assert policy?
        rtn: policy.get()

      calculatePayroll()
        -> employees as List of Employee

        stdout <- Stdout()
        stdout.println("Calculating Payroll")
        stdout.println("===================")
        for employee in employees
          stdout.println("Payroll for: " + $employee)
          stdout.println("- Check amount: " + $employee.calculatePayroll())
          if employee.address()?
              stdout.println("- Sent to:")
              stdout.println($employee.address())
          stdout.println("")

    PayrollPolicy with trait of IPayrollCalculator as abstract
      hoursWorked as Integer: 0

      override trackWork()
        -> hours as Integer
        hoursWorked += hours

      hoursWorked()
        <- hours as Integer: hoursWorked

    HourlyPolicy extends PayrollPolicy
      hourRate as Money: Money()

      HourlyPolicy()
        -> hourRate as Money
        this.hourRate = hourRate

      override calculatePayroll()
        <- amount as Money: hourRate * hoursWorked()

    SalaryPolicy extends PayrollPolicy as open
      weeklySalary as Money: Money()

      SalaryPolicy()
        -> weeklySalary as Money
        this.weeklySalary = weeklySalary

      override calculatePayroll()
        <- amount as Money: weeklySalary

    CommissionPolicy extends SalaryPolicy
      commissionPerSale as Money: Money()

      CommissionPolicy()
        ->
          weeklySalary as Money
          commissionPerSale as Money

        super(weeklySalary)
        this.commissionPerSale = commissionPerSale

      override calculatePayroll()
        <- amount as Money: super.calculatePayroll()
        amount += commission()

      private commission()
        <- amount as Money: commissionPerSale/5.0 * hoursWorked()

    EmployeeDatabase
      productivitySystem as ProductivitySystem: ProductivitySystem()
      payrollSystem as PayrollSystem: PayrollSystem()
      addresses as AddressBook: AddressBook()

      employees()
        <- rtn as List of Employee: List()

        rtn += createEmployee(1, "Mary Poppins", "manager")
        rtn += createEmployee(2, "John Smith", "secretary")
        rtn += createEmployee(3, "Kevin Bacon", "sales")
        rtn += createEmployee(4, "Jane Doe", "factory")
        rtn += createEmployee(5, "Robin Williams", "secretary")

      private createEmployee()
        ->
          id as Integer
          name as String
          theRole as String
        <-
          rtn as Employee: Employee()

        address <- addresses.getEmployeeAddress(id)
        employeeRole <- productivitySystem.getRole(theRole)
        payrollPolicy <- payrollSystem.getPolicy(id)

        rtn: Employee(id, name, address, employeeRole, payrollPolicy)

  defines program
    Demonstration()
      productivitySystem <- ProductivitySystem()
      payrollSystem <- PayrollSystem()

      employeeDatabase <- EmployeeDatabase()
      employees <- employeeDatabase.employees()

      productivitySystem.track(employees, 40)
      payrollSystem.calculatePayroll(employees)
//EOF

The driver components

The 'Demonstration' program, 'EmployeeDatabase', 'ProductivitySystem' and 'PayrollSystem' classes are really just artefacts that are needed to demonstrate how the solution works. The output is shown below (the same as the Python example - except Money is used and so you see the currency).

The Output

Tracking Employee Productivity
==============================
Employee 1 - Mary Poppins:
 - screams and yells for 40 hours.

Employee 2 - John Smith:
 - does paperwork for 40 hours.

Employee 3 - Kevin Bacon:
 - expends 40 hours on the phone.

Employee 4 - Jane Doe:
 - manufactures gadgets for 40 hours.

Employee 5 - Robin Williams:
 - does paperwork for 40 hours.


Calculating Payroll
===================
Payroll for: 1 - Mary Poppins
- Check amount: 3000.00#USD
- Sent to:
121 Admin Rd.
Concord NH 03301

Payroll for: 2 - John Smith
- Check amount: 1500.00#USD
- Sent to:
67 Paperwork Ave
Manchester NH 03101

Payroll for: 3 - Kevin Bacon
- Check amount: 1800.00#USD
- Sent to:
15 Rose St
Apt. B-1
Concord NH 03301

Payroll for: 4 - Jane Doe
- Check amount: 600.00#USD
- Sent to:
39 Sole St.
Concord NH 03301

Payroll for: 5 - Robin Williams
- Check amount: 360.00#USD
- Sent to:
99 Mountain Rd.
Concord NH 03301
        

Summary

The key points for this example are:

The automatic delegation of methods on 'Employee' mean that the methods do not need implementing.

While the main purpose of this example is not to compare the EK9 language to Python, it is quite clear that EK9 has been inspired by Python in terms of layout and syntax. But also inspired by Java/C#/Scala and C++ in terms of structure and explicit extension/implementation. It's not enough just to define method signatures that match up, an actual trait must be declared and used. This is a good thing; as it is explicit and type safe (it is not possible to pass the wrong type of object just because the method signatures match!).

SOLID

The SOLID principle as been observed quite well in the example.

Advantages

Should alterations to any of the implementations referenced above be needed, only a small part of the code would need to be adjusted. The effects of such changes are quite direct and obvious. In short; it is obvious where to look to alter 'manager' pay or 'secretary' activity text.

Changes to addresses would be done in the 'Address Book'.

Issues

Output is hardwired to 'Stdout', What about adding new employees? Altering an existing employee job role, adding a new job role? This solution does not address these functions. But more importantly it does not facilitate the development of such functions. It's not obvious where or how they could be implemented.

There are also a couple of issues in the code around 'ProductivitySystem'. The 'EmployeeDatabase' is directly coupled to 'ProductivitySystem'. It is also directly coupled to 'PayrollSystem' and 'AddressBook'!

The 'ProductivitySystem' seems to have mixed responsibilities. Is it just for tracking productivity; or is it also some type of HR system that is to be used for defining what roles should be available for a notional title? This is very confused and should really be separated out from the 'ProductivitySystem'.

The 'AddressBook' is only used by the 'EmployeeDatabase', which is fine in of itself. But this means that it cannot be easily swapped out for some type of database or file or remote service. The 'EmployeeDatabase' is directly linked to this implementation of the 'AddressBook'.

This page has now moved into the realm of software design, rather than just the EK9 language. But will be used as an example of how to use other EK9 language features to address the above issues.

The intention here is not to criticize the example from the web site (thank you Isaac Rodriguez for taking the time to produce such a good example), but really to show how the EK9 language features can be used in different ways. In fact most of the issues raised here were not really the main subject of the web site, but are really the 'Driver' infrastructure.

Revisiting

The example above will be revisited in the Components section, where it will be refined further. Then in the section on dependency injection more alterations and alternatives will be discussed.

Next Steps

The use of inheritance is covered in the next section. Components will also be of interest; as they too enable composition, but into larger building blocks.