Tony Marston's Blog About software development, PHP and OOP

Object Interfaces are EVIL

Posted on 2nd December 2023 by Tony Marston
Introduction
The "need" for object interfaces
What is wrong with inheritance?
PHP does not need object interfaces
References
Comments

Introduction

When I first ventured into OO programming with PHP 4 it did not contain the keywords "interface" and "implements", yet I still managed to produce effective software. As time went on and I read more articles in the hope of increasing my skills, but I kept coming across such phrases as implement the interface and program to the interface, not the implementation which I found confusing. When these keywords were added in PHP 5 I could not understand their purpose as they did not actually do anything. The description for object interfaces in the PHP manual was not very informative:

Object interfaces allow you to create code which specifies which methods a class must implement, without having to define how these methods are implemented.

Among its purposes it listed the following:

The phrase may be used interchangeably sounded suspiciously like dependency injection which draws its power from polymorphism, but as I had already achieved massive amounts of polymorphism by having each of my concrete table classes inherit from a single abstract class I was at a loss to see what benefits object interfaces brought to the table. The example of multiple database access services was even more confusing as I had already created two candidate classes for my Data Access Object which allowed me to switch between the original mysql API and the "improved" mysqli API. Both of these classes implemented the same set of method signatures, so it was easy to switch from one class to the other - all without the use of either inheritance or object interfaces. This led me to ask a very simple question - If I can achieve what object interfaces were designed to deliver without actually using them, what is the advantage of using them? I tried running my code both with and without using the keywords "interface" and "implements", but there was absolutely no difference. This led me to the inevitable conclusion that the use of object interfaces in PHP was totally pointless as they don't actually do anything except become a waste of keystrokes. This also added to my growing suspicion that much of what is written about OOP is nothing but hype and that most of its proponents are know-nothing dogmatists who are in great danger of becoming nothing more than Cargo Cult Programmers.

When I started learning about OOP I understood that it was based around just three concepts - Encapsulation, Inheritance and Polymorphism - so where did Interfaces come from and what part did they play? I tried for years to find the real reason why they were created, but to no avail. All I kept reading were the same useless phrases being regurgitated over and over again.

The "need" for object interfaces

Object interfaces were designed to provide polymorphism in statically typed languages without the need for inheritance.

It wasn't until I came across an article called Polymorphism and Inheritance are Independent of Each Other that the true story was revealed. It would appear that object interfaces were designed to provide polymorphism in statically typed languages without the need for inheritance, as indicated in the following code samples:

// C++ polymorphism through inheritance
class Car {
    // declare signature as pure virtual function
    public virtual boolean start() = 0; 
}

class VolkswagenBeetle : Car {
    public boolean start() {
        // implementation code
    }
}

class SportsCar : Car {
    public boolean start() {
        // implementation code
    }
}

// Invocation of polymorphism
Car cars[] = { new VolkswagenBeetle(), new SportsCar() };

for( I = 0; I < 2; i++)
     Cars[i].start();

The cars array is of type Car and can only hold objects that derive from Car (VolkswagenBeetle and SportsCar) and polymorphism works as expected. However, suppose I had the following additional class in my C++ program:

// C++ lack of polymorphism with no inheritance
class Jalopy {
    public boolean start() {
        // implementation code
    }
}

// Jalopy does not inherit from Car, the following is illegal
Car cars[] = { new VolkswagenBeetle(), new Jalopy() };

for( I = 0; I < 2; i++)
     Cars[i].start();

At compile time this will generate an error because the Jalopy type is not derived from Car. Even though they both implement the start() method with an identical signature, the compiler will stop me because there is a static type error.

A big problem I see with this code sample is that it is only appropriate in software which actually controls a vehicle in the real world. This is totally inappropriate in a database application as it does not communicate with physical objects in the real world, it only deals with information about those objects in a database, and that information is held in objects called tables. Furthermore, the only operations which can be performed on a database table, regardless of what informatioin it holds, are Create, Read, Update and Delete (CRUD). Nobody who writes an application which stores information on cars would ever include a "start" method.

What is wrong with inheritance?

Problems with inheritance are caused either by poor language design or bad programming practices

The aforementioned article raised the following question in my mind - if object interfaces were designed to create polymorphism without inheritance, then what is wrong with inheritance? That article had this to say:

Strong type checking imposed at compile time means that all polymorphism has to come through inheritance. This leads to problems with deep inheritance hierarchies and multiple inheritance where there are all kinds of problems with unexpected side effects. Even moderately complex programs become very hard to understand and maintain in C++.
[....]
The negative effects of the tight link between inheritance and polymorphism lead both Java and C# to introduce the concept of interface to pry apart the ideas of inheritance and polymorphism but keep strong type checking at compile time.
[....]
When type checking is deferred to runtime you can end up with strange behaviors when you make method calls to objects that don't implement the method, i.e. sending start() to an object with no start() method.
[....]
The one caveat to pure polymorphism is that we may develop subtle bugs that can be difficult to track down and fix. Pure polymorphism is only worth seeking if the language that you are using can reliably throw an exception when a method is not implemented.

As you should be able to see the "problem" for which object interfaces were the solution concerned the following:

This tells me that object interfaces were created as a technique to circumvent a problem which existed in the earliest OO languages, and some bright spark invented a principle which says that you MUST keep using this technique even though the problem no longer exists in modern languages. This has a distinct smell of faecal matter to me.

That article also contains the following statements:

The original compiled languages (C++, etc) performed static type checking because of performance issues.

C++ was dominant until the mid 1990s simply because it was an object oriented solution that was NOT interpreted. This meant that on the slow CPUs of the time it had decent performance. We used C++ because we could not get comparable performance with any of the interpreted object-oriented languages of the time, i.e. Smalltalk.

It was only during the time frame when Java and C# were introduced that CPU power was sufficient for interpreted languages to give sufficient performance at run time. The transition from having polymorphism and inheritance tightly coupled to being more loosely coupled depended on run time interpreters being able to execute practical applications with decent performance.

He concludes with the following statement:

Effective programmers are seeking polymorphism and not inheritance. The benefits of pure polymorphism outweigh any advantage that compile time type checking provides, especially when we have access to very sophisticated debuggers and support for runtime exception handling. In general, I believe that the benefits of pure polymorphism outweigh the value of static type checking.

Since the purpose of OOP is supposed to produce more reusable code which therefore requires less maintenance, I had already produced vast amounts of polymorphism in my RADICORE framework by creating an abstract table class which is then inherited by every one of my hundreds of concrete table classes. This follows the idea of programming-by-difference which I discovered when I read the paper Designing Reusable Classes which was published by Ralph E. Johnson & Brian Foote in 1988. I discuss this paper in my own article The meaning of "abstraction".

The theory behind the idea of programming-by-difference is that you examine a collection of classes looking for similarities and differences with a view to putting the similarities in an abstract class and isolating the differences to a series of subclasses. In a database application with a separate class for each table, each of these table classes is subject to exactly the same set of CRUD methods, so these methods are prime candidates for being inherited from an abstract class instead of being duplicated within each table class. This then opens up the possibility of implementing the Template Method Pattern so that the differences within each table subclass can be supplied using a series of "hook" methods. The fact that every method called from a Controller on a Model is a Template Method means that my entire framework is built around reusable Templates.

Inheritance can produce vast amounts of reusable code while interfaces produce none at all, so why promote something which is incapable of providing what OOP is supposed to provide?

Polymorphism is not just about sharing method signatures without implementations as you are not sharing any code at all, you are just duplicating the method signatures. Inheritance allows you to share method signatures as well as the code behind those signatures. The Template Method Pattern allows you to share invariant methods in the superclass which contain sharable code and mix them with variable or "hook" methods which can be customised within each subclass. Thus inheritance helps fulfil the purpose of OOP while object interfaces do not.

PHP does not need object interfaces

PHP can provide polymorphism without either inheritance or interfaces, so interfaces are not necessary. Doing something which is not necessary is a violation of YAGNI.

This problem does not exist in PHP as it is dynamically typed. It has always been possible to define several classes with the same method signatures without the use of the keywords "extends" or "implements" and have those methods called in a polymorphic manner. This is precisely what I did when I created the Data Access Objects to deal with the different DBMS engines which I support.

If object interfaces are totally unnecessary in PHP, then why do so many people keep insisting that they are a "must have"? If they use a feature which they don't need then why don't they realise that they are violating YAGNI? How can my critics complain that I am not following "best practices" when YAGNI is one of those practices which I am following and they are not? Telling me to do something which they fail to do themselves is a bit hypocritical, is it not?

Using a feature that was designed to provide polymorphism but not actually providing any polymorphism is a double violation of YAGNI.

It that isn't bad enough, when you consider that object interfaces were designed expressly to provide polymorphism without inheritance, and polymorphism only exists when the same interface is implemented in multiple classes, then if you create an interface which is only ever implemented in a single class then this is a double violation of YAGNI. Think about it. Using something that was designed to provide polymorphism, then using it WITHOUT providing polymorphism cannot by any stretch of the imagination be described as a "best practice".

Here endeth the lesson. Don't applaud, just throw money.


References


counter