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

Subclasses vs Subtypes

Posted on 4th July 2025 by Tony Marston
Introduction
Misleading answers
Words from Johnson and Foote
How I built my framework
How I used encapsulation
How I used inheritance
How I utilised polymorphism
Levels of reusability
My clash with "best practices"
Class Hierarchies
User-Defined types
Strict vs Dynamic typing
Conclusion
References
Comments

Introduction

The purpose of this article is to answer the question What is the difference between a subclass and a subtype? Some people claim that subclassing establishes a syntactic relationship while subtyping establishes a semantic relationship. Others claim that subclassing is for sharing code while subtyping provides polymorphism. I do not care for flowery language or theoretical differences, I prefer simple practicalities, and in practice both of these concepts boil down to exactly the same thing - they involve defining a set of method signatures in a superclass/supertype which can then be shared in any number of subclasses/subtypes. It is this sharing of method signatures which provides polymorphism as this provides the ability to instantiate any one of those subclasses/subtypes into an object before calling one of those shared methods. It does not matter that some of those shared methods may already contain implementations while others require you to provide implementations of your own, it is the sharing of method signatures which is the critical factor. Everything else is just a minor detail.


Misleading answers

These are some of the opinions I found by asking Google the following question: in oop what is the difference between subclass and subtype

  1. This is what the AI Overview provided:
    In object-oriented programming, a subclass is a class that inherits from another class (its superclass), while a subtype is a type that can be used in place of another type (its supertype). While often overlapping, these concepts are distinct: a subclass relationship doesn't always guarantee a subtype relationship.

    Subclass:

    • A subclass is created through inheritance, extending or specializing the functionality of its superclass.
    • It inherits the attributes and methods of its superclass.
    • A subclass can add its own unique methods and attributes, and it can also override methods inherited from the superclass.
    • In many languages, public inheritance implies a subclass relationship

    Subtype:

    • A subtype is a type that can be used wherever its supertype is expected, without causing errors.
    • This substitutability is a key characteristic of subtypes.
    • A subtype relationship is often, but not always, established through inheritance.
    • For example, if class B is a subtype of class A, any operation valid for A should also be valid for B.

    Key Differences:

    Focus:

    Subclassing focuses on code reuse and extension through inheritance, while subtyping focuses on substitutability and behavior preservation.

    Relationship:

    Subclassing is a syntactic concept (related to class definitions), while subtyping is a semantic concept (related to type relationships and behavior).

    Implication:

    A subclass is not always a subtype. For example, private inheritance in C++ creates a subclass but not a subtype because it doesn't ensure substitutability.

    While this answer uses clever words to describe the difference, such as one is a syntactic concept while the other is semantic, it is ignoring some basic facts:

    The only reason why a subclass cannot be regarded as a subtype is if the language is strictly typed as it will only allow polymorphism between objects of the same type. PHP is not strictly typed, so this restriction does not apply. Polymorphism exists when different objects, regardless of their type, share the same method signatures. When a method is called PHP does not check the object's type, it checks that the method exists.

  2. The following appeared in Why is it bad to have one mechanism for inheritance and subtyping? on Software Engineering Stack Exchange:
    It is quite well-known that (class) inheritance and subtyping (sometimes called interface inheritance) are different things: inheritance is a mechanism for sharing code, while subtyping is a relationship that allows an object of a subtype to be substituted where an object of a supertype is required, which is a kind of polymorphism.

    This "fact" is only well-known among those who have not been properly educated. Class inheritance and subtyping are only different if the language forces them to be different. In strictly typed languages polymorphism can be restricted to objects of the same type, but in PHP this restriction does not exist. In my own framework every concrete table class (and I have hundreds) inherits from the same abstract table class, so they all share the same method signatures. This means that where I have code that calls these methods I can nominate any one of those hundred objects.

  3. The following appeared in What's the difference between a subclass and a subtype? on Software Engineering Stack Exchange:
    Subtyping is a form of type polymorphism in which a subtype is a datatype that is related to another datatype (the supertype) by some notion of substitutability, meaning that program elements, typically subroutines or functions, written to operate on elements of the supertype can also operate on elements of the subtype.

    Subclassing should not be confused with subtyping. In general, subtyping establishes an is-a relationship, whereas subclassing only reuses implementation and establishes a syntactic relationship, not necessarily a semantic relationship (inheritance does not ensure behavioral subtyping).

    To distinguish these concepts, subtyping is also known as interface inheritance, whereas subclassing is known as implementation inheritance or code inheritance.
    Wording such as this merely identifies theoretical differences between subtyping and subclassing which may exist in some of those languages which are strictly typed. If these descriptions do not apply to my current language which does not restrict polymorphism to objects with the same type then they are irrelevant.
  4. The following appeared in Differences between subtypes and subclasses from Princeton University:
    There are important differences between subtypes and subclasses in supporting reuse. Subclasses allow one to reuse the code inside classes - both instance variable declarations and method definitions. Thus they are useful in supporting code reuse inside a class. Subtyping on the other hand is useful in supporting reuse externally, giving rise to a form of polymorphism. That is, once a data type is determined to be a subtype of another, any function or procedure that could be applied to elements of the supertype can also be applied to elements of the subtype.
    The idea that subclassing only allows internal reuse while subtyping only allows external reuse is completely wrong. Both allow the sharing of the same method signatures, and the implementations which they may contain, within a number of different objects, and these methods can be called either internally or externally without any form of restriction.

Let me make this perfectly clear - polymorphism is defined as same interface [method signature], different implementation, so if the same method signature is contained within several objects then a piece of code which calls that signature will work with any one of those objects with the expectation that it will produce different results. PHP is not strictly typed, therefore it makes no attempt to restrict polymorphism to objects of the same type. The only criterion is that the method being called actually exists within that object. If a shared method already contains an implementation then that is a bonus.


Words from Johnson and Foote

At least a decade after developing my framework I came across a paper called Designing Reusable Classes which was published in 1988 by Ralph E. Johnson & Brian Foote which contained the following statements:

Protocol

The specification of an object is given by its protocol, i.e. the set of messages that can be sent to it [methods which can be called].
Objects with identical protocol are interchangeable. If several classes define the same protocol then objects in those classes are "plug compatible".
Standard protocols are given their power by polymorphism.

Inheritance

Most object-oriented programming languages have another feature that differentiates them from other data abstraction languages; class inheritance. Each class has a superclass from which it inherits operations and internal structure. A class can add to the operations it inherits or can redefine inherited operations. However, classes cannot delete inherited operations.

Class inheritance has a number of advantages. One is that it promotes code reuse, since code shared by several classes can be placed in their common superclass, and new classes can start off having code available by being given a superclass with that code. Class inheritance supports a style of programming called programming-by-difference, where the programmer defines a new class by picking a closely related class as its superclass and describing the differences between the old and new classes. Class inheritance also provides a way to organize and classify classes, since classes with the same superclass are usually closely related.

One of the important benefits of class inheritance is that it encourages the development of the standard protocols that were earlier described as making polymorphism so useful. All the subclasses of a particular class inherit its operations, so they all share its protocol. Thus, when a programmer uses programming-by-difference to rapidly build classes, a family of classes with a standard protocol results automatically. Thus, class inheritance not only supports software reuse by programming-by-difference, it also helps develop standard protocols.

Another benefit of class inheritance is that it allows extensions to be made to a class while leaving the original code intact. Thus, changes made by one programmer are less likely to affect another. The code in the subclass defines the differences between the classes, acting as a history of the editing operations.


Abstract Classes

Standard protocols are often represented by abstract classes

An abstract class never has instances, only its subclasses have instances. The roots of class hierarchies are usually abstract classes, while the leaf classes are never abstract. Abstract classes usually do not define any instance variables. [see note #1 below]. However, they define methods in terms of a few undefined methods that must be implemented by the subclasses. [see note #2 below]

A class that is not abstract is concrete. In general, it is better to inherit from an abstract class than from a concrete class. A concrete class must provide a definition for its data representation [see note #3 below], and some [all] subclasses will need a different representation. Since an abstract class does not have to provide a data representation, future subclasses can use any representation without fear of conflicting with the one that they inherited.


Rule 8: Subclasses should be specializations.

There are several different ways that inheritance can be used. Specialization is the ideal that is usually described, where the elements of the subclass can all be thought of as elements of the superclass. Usually the subclass will not redefine any of the inherited methods, but will add new methods.

An important special case of specialization is making concrete classes. Since an abstract class is not executable, making a subclass of an abstract class is different from making a subclass of a concrete class. The abstract class requires its subclasses to define certain operations, so making a concrete class is similar to filling in the blanks in a program template. An abstract class may define some operations in an overly general fashion, and the subclass may have to redefine them. [see note #4 below]

Notes from the RADICORE implementation:

  1. The abstract table class, which is inherited by every concrete table class, defines a set of common table properties which are initially empty.
  2. There is no limit to the number or kind of methods, either abstract or non-abstract, which an abstract class may contain.
  3. The specifications of each concrete table class are loaded when the class constructor is executed. Each subclass may override any of the "hook" methods to provide custom processing at run time.
  4. Using an abstract class allows the Template Method Pattern to be implemented, which means that it can contain a mixture of invariable methods with standard implementations which are inherited, and empty "hook" methods which may be overridden with customised implementations in individual subclasses. This implements the Hollywood Principle which is an essential ingredient in a framework.

Notice that Johnson and Foote make no mention of subtyping, or that it is any different from subclassing, as it is the sharing of common protocols which makes objects "interchangeable" and "plug compatible". The idea that polymorphism could be restricted to objects of the same type did not exist in their day, so I can only assume that it was added on by someone who thought he was being clever but who turned out to be the opposite. They also advise to only inherit from an abstract class and not a concrete class as this will prevent inheriting an implementation which could cause problems.


How I built my framework

When in 2002 I came to rebuild my framework for building enterprise applications using PHP my aim was to take advantage of its object-oriented capabilities to increase code reuse and decrease code maintenance. I followed those practices which supported this objective and ignored those which did not. I did not go on any training courses to learn from the "experts", I simply read the online manual which explained the mechanics of Encapsulation (creating classes) and Inheritance (using the "extends" keyword). It said nothing about Polymorphism or how it could be used, so I had to take an educated guess. I assumed it had something to do with the fact that the same method name could be defined in multiple classes whereas a procedural function can only be defined once. Knowing how to create polymorphism is one thing, but knowing how to take advantage of it is something else entirely.

How I used encapsulation

I decided to start by building a small sample application as a proof of concept (PoC) so that I could test various ideas to see what worked and what didn't. Having become familiar with the 3-Tier Architecture in my previous language I decided to continue using this for my new codebase. This was easy as OOP requires a multi-tier architecture by default - once you have created a class with methods you need a separate piece of code to instantiate that class into an object so that it can call those methods.

As I had split my Presentation layer component into two parts, one dealing the with the HTTP request and another dealing with the response, I was later informed by a colleague that I had also implemented a version of the Model-View-Controller design pattern. When combined with the 3-Tier Architecture this produces the structure shown in Figure 1 below:

Figure 1 - Model-View-Controller combined with the 3 Tier Architecture

Model View Controller Data Access Object Presentation layer Business layer Data Access layer model-view-controller-03a (5K)

Note that all the boxes in the above diagram are hyperlinks which will take you to a more detailed description.

The components in the RADICORE framework fall into the following categories:

Note that Controllers, Views and DAOs are reusable components which are built into the framework. Models have to be generated by the developer using facilities provided within the Data Dictionary. Each table class is initially built from a standard template which inherits all its common code from an abstract table class which is also built into the framework.

How I used inheritance

When I created the classes for the first database tables in my sample application there were several ideas I implemented which had a dramatic effect on the amount of code which I could share through inheritance. I did not know it at the time but I was mirroring what had been written in Designing Reusable Classes, which was published in 1988 by Ralph E. Johnson & Brian Foote, in which they described a technique which they called programming-by-difference. This involves first writing code that works, then examining it looking for similarities and differences with the aim of moving the similarities into reusable modules, such as abstract classes, and separating out the differences in unique modules such as concrete subclasses. If several classes share the same protocol then objects in those classes are "plug compatible" and can be swapped around at run-time. This provided me with a more meaningful description of that nebulous process called Abstraction which actually can be condensed to separating the similar from the different, the abstract from the concrete. Identifying objects in an application which share the same protocols was blindingly obvious to me - every table in a database, regardless of what data it holds, is subject to exactly the same Create, Read, Update and Delete (CRUD) operations. I expanded on this simple fact with the following ideas:

  1. While some of the code samples published by other programmers had separate methods to load(), validate() and store() I decided that if I was always going to call those methods in that sequence then it would be good practice to place those calls in a separate wrapper method, just as I had done decades earlier in my COBOL library, thus replacing 3 calls with just one. This turned out to be a very good idea as I was later able to make global changes to my codebase simply by amending existing wrappers or creating new ones.
  2. I did not construct method names which were specific to particular entities, such as insertProduct(), insertCustomer() and insertInvoice() as I wanted to make use of the fact that OOP allowed me to reuse the same method names in multiple classes, so instead I created method names which were more generic, such as insertRecord(), updateRecord() and deleteRecord() which could be used on the product object, the customer object or the invoice object, or indeed any table object in my application.
  3. I did not create a separate class property for each column in a table as that would mean creating separate code to move the column data into and out of a table object, and this struck me as being too convoluted as well as unnecessary. I had already noticed that the data from external sources, such as from HTML pages or SQL databases, arrived in the form of an array, so being an avid follower of the KISS principle I decided the keep all table data in its array so that I could pass all that data around in a single variable as both an input and output argument. I later discovered that this was a shining example of loose coupling which is supposed to be better that tight coupling.
  4. In order to share the common CRUD operations within the first table class with the second table class I did not make the mistake of inheriting from the first table class and then changing the implementation, I transferred those methods to a separate generic (abstract) class which I could then inherit into every concrete table class. This ensured that I never inherited an implementation that I did not want. While moving all the common methods to an abstract class I isolated all the characteristics which differentiate one table from another and created a set of common table properties. These are initially empty and are only filled with values when a concrete subclass is instantiated using code in the constructor method.
  5. When I later realised that there may be times when I would want to augment the standard processing with some custom rules within a particular table class I turned to the ability of a subclass to override a method in the superclass. At the places in the abstract superclass where I may want to interrupt the processing flow I made calls to various new methods in the wrapper methods which I had previous created which did nothing, but which could be overridden in any subclass to do something specific. Each different subclass could therefore provide its own unique behaviour. I later discovered that these customisable methods were actually called "hook" methods and were part of the Template Method Pattern.

Inheritance is the primary method in OOP for sharing or reusing code. In means that you can take all the properties and methods in one class (known as the superclass) and share them in another class (known as the subclass). When this subclass is instantiated into an object that object will be a combination of the methods and properties of both classes. It is not possible to "uninherit" a property or method in the subclass, but it is possible to override (replace) a method in the superclass with a method in the subclass with the same signature but a different implementation. At run-time if a method is called which was not defined in the subclass the system will look for that method in the superclass.

It is bad practice to inherit from a concrete class as this may contain implementations which could cause problems in a subclass. It is therefore recommended to only inherit from an abstract class as this should not contain any problematic implementations. The advantage of inheriting from an abstract class while it can contain invariant with fixed implementations it can also contain calls to variable/customisable methods which can be overridden with different implementations within individual subclasses.

With this architecture I had used inheritance by making every concrete table class to be a subclass of the same abstract superclass.

How I utilised polymorphism

Having lots of objects which contain the same method signatures is one thing, but what is the point? If all these objects are "plug compatible" then how can you provide a mechanism which swaps one object for another at run-time? When I created my first couple of Controller scripts I had code similar to the following:

<?php
require 'screens/person.detail.screen.inc';
require 'classes/person.class.inc';
$dbobject = new Person(); 
$dbobject->setUserID    ( $_POST['userID'   ); 
$dbobject->setEmail     ( $_POST['email'    ); 
$dbobject->setFirstname ( $_POST['firstname'); 
$dbobject->setLastname  ( $_POST['lastname' ); 
$dbobject->setAddress1  ( $_POST['address1' ); 
$dbobject->setAddress2  ( $_POST['address2' ); 
$dbobject->setCity      ( $_POST['city'     ); 
$dbobject->setProvince  ( $_POST['province' ); 
$dbobject->setCountry   ( $_POST['country'  ); 

if ($dbobject->insertPerson($db) !== true) { 
    // do error handling 
} 
?> 

You should observe that this is a prime example of tight coupling as each particular Controller is tied to a particular Model, a particular screen definition and a particular set of column names. In order to make each Controller reusable I had to convert each of those hard-coded definitions into a variable which could be supplied at run-time. I did this by moving the setting of these variables into a separate component script and changed the controller to reference these variables, as shown in the following example:

<?php  // component script
$table_id = "person";           // identify the Model
$screen   = 'person.detail';    // identify the View
require 'std.add1.inc';         // activate the Controller
?> 

<?php  // page controller script for the ADD1 pattern
require 'classes/$table_id.class.inc';
require 'screens/$screen.screen.inc';
$dbobject = new $table_id;
$result = $dbobject->insertRecord($_POST);
if ($dbobject->errors) {
    // do error handling 
}
?> 

This is now an example of loose coupling as none of the controllers contains hard-coded references to any particular implementations. It should be noted that while each different user transaction will have its own unique component script they can all reference any of my built-in controller scripts. There is a separate Controller for each of my Transaction Patterns.

Loose coupling is supposed to be better than tight coupling as described in The difference between Tight and Loose Coupling.

Constructing the HTML output has also been reduced to a single reusable component which uses standard code such as that in the following example:

<?php  // controller script
...
// build list of objects for output to XML data
$xml_objects[]['root'] = &$dbobject;

// build XML document and perform XSL transformation
$view = new radicore_view($screen_structure);
$html = $view->buildXML($xml_objects, $errors, $messages);
echo $html;
?> 

This buildXML() method performs the following steps:

Levels of reusability

The entire structure of the components in the RADICORE framework can be summarised in Figure 1 above. There is also a more detailed diagram available.

The framework has the following reusable components:

The RADICORE framework is not just a collection of library functions which need to be called by the developer, it is a true framework as it implements the Hollywood Principle (don't call us, we'll call you). The framework itself consists of the following subsystems:

The framework is a modular system which is comprised of a number of integrated subsystems, each of which has its own database, its own directory structure in the file system, and its own entries in the framework database. New application subsystems can be added at any time by following these steps:

  1. Create a new subsystem.
  2. Build the directory structure for that subsystem.
  3. After creating your database import its details into the Data Dictionary.
  4. For each table:
    1. Export those details to create the class file for each database table.
    2. Define any relationships between tables and export the amended table structure file.
    3. Generate tasks by selecting a table and linking it to a Transaction Pattern.
    4. Modify screen labels, titles and button text (optional)
  5. Custom processing can be added to any Model by overriding any of the "hook" methods.

The speed at which I could develop new application components and the facilities which I could provide as standard within each component made me believe that my foray into the world of object-oriented programming had been successful as I had used the facilities provided by Encapsulation, Inheritance and Polymorphism to increase code reuse and decrease code maintenance. A huge amount of this reusability came about because of the fact that I had created a single abstract table class which provided methods which were inherited by every concrete table class. Having the same method signatures available in multiple classes is what provides polymorphism, and I took advantage of this by implementing a new type of Dependency Injection. This made it possible to create my library of 45 reusable controllers which can be linked to any one of my 400 concrete subclasses to create 45 x 400 = 18,000 (yes, EIGHTEEN THOUSAND) opportunities for polymorphism.

This abstract class, while providing a huge amount of standard boilerplate code, then enabled me to implement the Template Method Pattern on every method which is called from a Controller to a Model. The use of this pattern made it easy for a developer to insert custom code into any subclass by utilising any of the numerous "hook" methods. In this abstract class there are over 200 invariant/fixed methods and over 100 variable "hook" methods.


My clash with "best practices"

Every programmer is told that they should follow these things called "best practices", but what are they and where are they published? There is no single source of truth as what is best for one group of programmers may be ignored by another. Different groups use different languages to develop different applications, and there is no "one size fits all" when it comes to recognising what practices are best. I have been designing and building database applications for over 40 years and have written development frameworks in three different languages, and while each language has been totally different I have managed to work with it and become more productive than before. I have learned to only follow those practices which have actual benefits, which means that there are some practices which I avoid, some of which are mentioned below:

Class Hierarchies

I first starting to hear warning bells in my head when I read about the IS-A test which is supposed to be used when creating class hierarchies. When I read such statements as A Car and a Train and a Truck can all inherit behavior from a Vehicle object, adding their subtle differences. A Firetruck can inherit from the Truck object, and so on

The idea is that the "IS-A" test identifies hierarchies of classes where each subclass/subtype inherits methods and properties from its superclass/supertype. In all these examples I see the "supertype" as being a table in a database, and each table has its own class. The idea that each "subtype" requires its own class strikes me as being nonsensical as I cannot see any justification for creating different classes for different rows in the same table. All I can see is the mass of code I would need to read a row from the database and then decide which class I would need to instantiate to hold its data. Rather than have a separate class for each subtype I would have a separate column on the table to identify its subtype. This is discussed further in the following:

I do not have multi-level class hierarchies for the simple reason that every one of my Model classes IS-A database table, and each database table shares exactly the same protocols as every other table. This simple observation caused me to put all these shared protocols into an abstract table class which is then inherited by every concrete table class, which results in a hierarchy only one level deep. I never inherit from one concrete class to create a different concrete class, so I never encounter the problems reported by other programmers. While I have a separate class for each table I would never contemplate having a separate class for different rows in the same table. The only reason I sometimes create a subclass of a concrete class is to provide a different implementation in some of the several "hook" methods. It is still the same physical table, but with different processing.

User-Defined types

It wasn't until several years after I had entered the world of object-oriented programming that I encountered the word user-defined type, which is nothing more than a record or a composite object. Before that time the only usage of the word "type" referred to data type which identified the primitive data types supported in database systems and programming languages. The problem is that those languages which are strictly typed now include user-defined type in the list of types which they can check, and for me this in not intuitive, goes against my previous 20 years of programming experience, and therefore not a practice which I am willing to follow. Whenever I see the word "type" mentioned I assume it is from a fixed list of data types which already have a known range of values which I can carry around in my head without have to look them up. User-defined types, on the other hand, fall outside of this category as their structures are invented by individual programmers. Because they are different I prefer to describe them using different terminology. This emphasises the fact that they are different and not almost-but-not-quite similar.

In PHP, which is dynamically typed, the only "types" which are recognised are shown in the results produced by the gettype() function. This means that objects and arrays have fixed types of "object" and "array" respectively. However, in strictly typed languages the purpose of giving everything a "type" is so that when a value is referenced you can verify that it has the correct type as early as possible. This is handled as follows:

None of the restrictions apply to PHP as any checking of a variable's type can only be performed at run time. I do not have to specify the "type" of object I am instantiating as its validity for being used in a polymorphic manner requires only that the method being called actually exists within that object. When it comes to inserting values into an array there are no restrictions. Thus I can create an indexed array or an associative array, or a mixture of both, with values of any type, without any restrictions. I can also create a multi-dimensional array and even a jagged array.

Strict vs Dynamic typing

Far too many of the principles and practices which are being followed by the novice programmers of today were developed in languages which work totally differently from PHP and are therefore not a good fit. Practices designed for strictly typed languages are also not a good fit as PHP, being dynamically typed, tackles the problem of type safety in a different way. Too many programmers are taught to believe that statically typed languages are automatically better than dynamically typed languages because they can detect type errors at compile time instead of run time, but they fail to understand that both tackle the potential for type errors in a different way. In this wikipedia article it says the following:

Type enforcement can be static, catching potential errors at compile time, or dynamic, associating type information with values at run-time and consulting them as needed to detect imminent errors, or a combination of both. Dynamic type enforcement often allows programs to run that would be invalid under static enforcement.

A statically typed language will detect a type mismatch at compile time and immediately terminate the compilation. On the other hand a dynamically typed language such as PHP can only detect a type mismatch at run time, but instead of terminating in error it has the ability to automatically convert the value into the correct type so that it can continue. This process is known as Type Conversion (called Type Juggling in PHP), and will only produce an error if the value cannot be converted. The RADICORE framework provides a mechanism for detecting and dealing with potential errors, as explained in How to avoid Type Errors.

Great efforts have been made by a bunch of dogmatists to add the option of strict typing into PHP in the belief that it will make PHP "better", but these people do not understand that the extra work involved in getting the language to perform these type checks is a total waste of time. They are forcing themselves, as well as the rest of the userland developers, to add code to perform manually what the language is perfectly capable of performing automatically. Adding code which is not strictly necessary cannot be justified. In fact it is a violation of the YAGNI principle. That is why I refuse to add any sort of type hinting and type enforcement into my code simply because I have learned how to take advantage of PHP's dynamic nature.


Conclusion

It should now be obvious that in reality there is no difference between a subclass and a subtype as they both provide the same set of method signatures in multiple classes. This makes those classes interchangeable and plug-compatible as the same methods can be called on multiple objects, and this fits the definition of polymorphism. It does not matter how those signatures become to be shared, whether it be from class inheritance, interface inheritance or simple code duplication, it just matters that they are shared. It does not matter whether the shared methods contain implementations or not, whether they are abstract or not, whether they can be overridden or not, it just matters that they are shared.

If there are some languages which place restrictions on polymorphism, such as only allowing objects of the same type to be accepted, then that is a an implementation detail which has been built into the language and is not part of the original concept.

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


References


counter