What is Subtype Polymorphism or Subtyping (Using Java & Scala)

What is Subtype Polymorphism (Using Java & Scala) - GuidingCode

Within the object-oriented programming (OOP) discipline, one of the main types of polymorphism that can be performed is subtyping or subtype polymorphism.

Therefore, this guide goes in-depth to explain the characteristics of subtype polymorphism, how to utilise it in the Java or Scala programming language and the objectives or advantages of applying it.

Let’s get right into it!

 

What is Subtype Polymorphism?

Sub-type polymorphism refers to the ability to use an object of a subclass or subtype wherever an object of a supertype or parent class is expected.

When discussing subtype polymorphism, note that supertypes correspond to superclasses or parent classes, and subtypes correspond to the subclass inheriting from that superclass. Hence, inheritance is a requirement or prerequisite to perform subtype polymorphism.

This represents an “is a” relationship between the two classes. Thus, every A (object of the subclass) is considered a B (instance of superclass). So, this allows objects of the subclass to be substitutable with objects of the superclass, whenever an object of the superclass is expected, in any context.

For instance, the following diagram represents an inheritance relationship between shape (superclass) and rectangle, triangle, circle and hexagon (subclasses). Thus, this satisfies the “is a” relationship, as a rectangle “is a” shape, and the same rule applies to the other subclass examples as well. Therefore, subtype polymorphism allows us to use an object of a rectangle in the place where an object of the shape is expected. This example will be further explored, utilised, and demonstrated in the subsequent section using the Java and Scala programming language.

inheritance relationship for subtype polymorphism or subtyping in object oriented programming

 

Suppose the client or application expects a reference to the superclass instance. However, with subtype polymorphism, you can use a subtype object as the function argument or anywhere the superclass instance is expected. So, there should be no surprise when the client sends a message to the object.

 

What is the Liskov Substitution Principle (LSP) in OOP and Subtyping?

By following the Liskov Substitution Principle (LSP), the instance of the superclass and subclass are substitutable depending on the class design. Based on LSP, objects of the superclass are substitutable or replaceable with objects of its subclasses, without resulting in client or application errors. Hence, superclass and subclass objects will behave similarly. For this, there are a few rules to follow, where the overridden method in the subclass should accept the same arguments and return the same value type as the corresponding superclass function.

 

What is the Principle of Least Astonishment (POLA) in OOP and Subtyping?

This is further supported by the Principle of Least Astonishment (POLA) or also known as the Principle of Least Surprise (PLS), where the object of the subtype ideally shouldn’t “surprise” the client by requiring more or returning less than the supertypes. Thus, the subclass can’t add additional methods nor remove or nullify methods from the superclass. Hence, functions and other elements available in the superclass can operate on objects or elements of the subtype

 

How to Use Subtype Polymorphism in Java or Scala?

1. Assign a Subclass’s Object to the Superclass’s Instance

One of the main and basic uses we see of subtype polymorphism is in a simple assigning statement where it’ll allow you to assign an object of a subclass to an instance of a parent (superclass).

 

Java Source Code:

public class test {
    public static void main(String[] args) {
        abstract class Shape{}
        class Rectangle extends Shape{}

        Rectangle r = new Rectangle();
        Shape s = r;
}}

 

Scala Source Code:

abstract class Shape(){}
class Rectangle extends Shape{}

object Main{
    def main(args: Array[String]): Unit = {
        val r: Rectangle = new Rectangle()
        val s: Shape = r
}}

 

For instance, in the above code, we’re assigning the object (r) of a subclass (Rectangle), to an instance (s) of a superclass (Shape).

 

2. Use a Subclass’s Object as the Superclass’s Function Argument

By extending from a parent class, when we call a method signature from that class, we can substitute or use any subclass object as the function argument which accepts objects where otherwise an instance of the superclass would be expected:

 

Java Source Code:

public class test {
    public static void main(String[] args) {
        abstract class Shape{}
        class Rectangle extends Shape{}
        class Triangle extends Shape{}
        class Circle extends Shape{}
        class Hexagon extends Shape{}

        public void callToString(Shape s) {
            System.out.println(s.toString());
        }

        callToString(new Rectangle("rectangle"));
        callToString(new Triangle("triangle"));
        callToString(new Circle("circle"));
        callToString(new Hexagon("hexagon"));
    }
}

 

Scala Source Code:

object Main{
    abstract class Shape(){}
    class Rectangle extends Shape{}
    class Triangle extends Shape{}
    class Circle extends Shape{}
    class Hexagon extends Shape{}

    def main(args: Array[String]): Unit = {
        callToString(new Rectangle(“rectangle”));
        callToString(new Triangle(“triangle”));
        callToString(new Circle(“circle”));
        callToString(new Hexagon(“hexagon”));

        def callToString(Shape s): Unit={
        println(s.toString())
    }
}}

 

3. Method Overriding, Dynamic Polymorphism

Additionally, based on the above code, assuming each subclass extends the superclass Shape, if we were to call the callToString(Shape s) method, it would invoke the toString method in each of the subclasses. Although a Shape type object is required, we can enter the instance of its subclass rectangle, circle, etc. as the argument, even though a superclass object is expected. Also, note that each of the toString methods has a different implementation.

Hence, in subtype polymorphism, the method can also be overridden in the subclass, by providing a different concrete method implementation. Thus, subtype polymorphism is closely related to method overriding or dynamic polymorphism.

Through this, it isn’t necessary for the developer to take note of the exact type of the object when calling the method, and only considering the object is an instance of a subclass of the superclass is sufficient. Subtype polymorphism will handle the correct execution or printing of the correct value.

 

Java Source Code:

public class test {
    public static void main(String[] args) {
       class Shape{
          String name;

          void draw(){
               System.out.println("This is a shape");
           }
      }

      class Rectangle extends Shape{
           @Override
           void draw(){
                System.out.println("This is a rectangle");
            }
        }
    }}

 

Scala Source Code:

class Shape(name: String){
    def draw(): Unit={
        println(“This is a shape”)
    }
}

class Rectangle(_name: String) extends Shape(_name: String){
    override def draw(): Unit={
        println(“This is a rectangle”)
    }
}

 

Referring to the above code, similarly, you can also create a generic code in the parent class, like a draw() function, which is also applied in the subclass, and the subclass can have a concrete implementation as well. The subclass method will be implemented as it is overridden. Hence, if we were to call s.draw(), it would return the outcome of the draw() method in the subclass Rectangle.

Hence, like dynamic polymorphism or method overriding, subtype polymorphism is performed or completed during runtime of the program, rather than during compile time like for static polymorphism or method overloading.

Overall, we can see when a subtype instance appears in the supertype context or where a supertype instance is expected, especially as an argument in a function call, this executes the supertype operation on the subtype object. Thus, the subtype’s version of the method implementation is executed, as in the above draw() function.

 

Advantages of Subtype Polymorphism or Subtyping

There are 3 main reasons we’ve identified, why one would use subtype polymorphism in an object-oriented system:

 

1. Enables Extensibility and Promotes Reuse of Code in the Program

Firstly, through inheritance, code written in the superclass can be reused in the subclass extending it. Thus, without having to write duplicate code, the subclass can inherit the methods and data fields in the parent. Then, through subtype polymorphism, the subclass can override these methods to provide its own concrete implementation. Hence a subclass can extend and override the existing methods by modifying it to behave uniquely for that subclass, without affecting existing system functions in the superclass. Thus, objects of different classes will respond and behave differently to the same message or function call.

This also makes it easier when creating additional subclasses, where we can inherit and extend from the same superclass.

 

2. Simplifies Code Maintenance

Additionally, reusing code makes it easier for the developer to troubleshoot the source of an error with the program execution. For instance, since methods are extended in the subclasses from a superclass, if there are errors when calling the particular method, we can check the code for that method in the superclass. Plus, you wouldn’t have to make the correction in all the subclasses which inherit from the superclass. Mostly, you’ll only need to make changes at the superclasses version of the method.

 

3. Enables the Open-Closed Principle (OCP) and Promotes Flexibility

According to the Open-Closed Principle (OCP) by Bertrand Meyer, software entities (in this case for subtype polymorphism, classes) should be open for extension but closed for modification. This relates to the Principle of Least Astonishment, which was explained earlier, where ideally the methods of the subclass shouldn’t require more or return less or make drastic changes from that in the superclass, thus “surprising” the client. This ensures the LSP is obeyed and the instances of the subclass and superclass are substitutable, allowing sub-type polymorphism.

Overall, through OCP, this ensures the code is flexible to functionality changes and additions, without modifying or altering the system functionalities.

Another way subtype polymorphism makes the program flexible, is as changes are made at the parent class (superclass), these changes will be applied to all the subclasses that extend it also.

 

Conclusion

To recap, subtype polymorphism is an object-oriented programming principle that entails the use of the subclass object where an instance of the superclass is expected. So, there are two principles that support this notion, which is the Liskov Substitution Principle (LSP) and the Principle of Least Astonishment (POLA). Therefore, subtyping can be employed for using a subclass object as an argument for superclass function calls and for overriding methods instantiated in the superclass to give each subclass a unique concrete implementation of the methods.

All in all, this guide explores what exactly is subtyping and subtype polymorphism, how to use it in Java and Scala, and the benefits of utilising it in your programming. We hope that this guide has helped you with using subtyping in your very own project.

Before we end, do comment down below, if you have any questions regarding this topic or if you have any additional information or experience to share regarding subtyping. We’ll love to hear it!

Feel free to share this post with your fellow coding enthusiasts or somebody you know that needs help with understanding subtype polymorphism.

Total
0
Shares
Leave a Reply

Your email address will not be published. Required fields are marked *

Related Posts
Total
0
Share