How to correctly use polymorphism to call methods of the right class in Java

Viewed 387

Suppose that I have a movie theater registration system.

And that I have a parent Customer class and a child MinorCustomer class.

MinorCustomer has a isAuthorized() method, which is not present in Customer, that returns true or false, meant to be called if the selected movie is not for unaccompanied minors

Now, when instantiating the classes to store the information in a data structure (whichever), I run into the issue that I cannot call isAuthorized() in the event that the client is a minor.

This is all hypothetical so there's no program with this code, but assuming the case would be

Customer cust = new Customer()
if(cust.getAge() < 18) {
    cust = (MinorCustomer) cust;
    cust.isAuthorized();
}

However, that code would not be valid, since it would still consider cust to be an instance of Customer, not MinorCustomer. I know that I could simply use an if statement to determine if I want to create a new instance of Customer/Minor depending on the age, but I'd like to take advantage of polymorphism to seamlessly change the type without having to write more rigid code.

4 Answers

Create a CustomerFactory that gives a customer of the appropriate type based on an age parameter instead.

Relying on the principle that

class Customer { }
class MinorCustomer extends Customer { }

...then an implementation of your factory could be:

class CustomerFactory {
    public static Customer createInstance(final int age) {
        if(age < 18) {
            return new MinorCustomer();
        } else {
            return new Customer();
        }
    }
}

...which can then be used in your code like so.

Customer cust = CustomerFactory.createInstance(17);

You can enforce instanceof checks in methods that require that the Customer is not a MinorCustomer, or you can do an age check on the customer since you can get that information for free anyway.

You're missing a key concept here: that a customer is a customer. Whether it's minor, gold, major, silver, credible, bankrupt; it's a customer. This is part of basic OOP object identification, in my opinion.

Behind these sentences that state the obvious is a fundamental concept: you must have a definition of "customer" and whatever you call customer must meet it, although it may remain free to choose "how" they meet it.

Now, to make that practical: if you have a Customer class that is extended by MinorCustomer and other sub-classes, you need to declare the common behavior that's applicable to all customers in the Customer class. Polymorphism hinges on this. If a "customer" knows of nothing like "is authorized", then you can't introduce polymorphic "is authorized" behavior.

To use an example:

class Customer {
    final int getAge() {
        //return
    }
    boolean isAuthorized() {
        return true;
    }
}
class MinorCustomer extends Customer {
    private boolean parentPresent;

    @Override
    boolean isAuthorized() {
        return this.parentPresent;
    }
} 

In the above code, the "definition" of a "customer" includes the concept of "being authorized". A "minor customer", however, is free to choose "how" it meets that definition.

It's as simple as that, you need your Customer API to declare the isAuthorized() method, and have that overridden in MinorCustomer. This, of course, may be inadequate for your design; and that's fine: it would simply mean that the place is not right for polymorphic behavior (and perhaps also that the inheritance relationship should be questioned).
And you can extend this reasoning to other examples as well, the parent-to-child relationship must include the declaration of behavior in the parent for polymorphism to be applicable.

As for the mechanics of cleanly creating related objects, Makoto's post gives a good approach.

You need to add isAuthrorized property in Customer class.

and logic should like this

    Customer cust = new Customer(...);
    if(cust.getAge() > 18){
      //skip authorization
      cust.isAuthrorized = true;
    }else{
      authorize.user(cust);
      //set isAuthrorized in authorization class based on user authentication fail or not
    }

Sample in C#

 abstract class Customer
{
    public string Name { get; private set; }

    public string Surname { get; private set; }

    public string Id { get; private set; }

    public Customer(string id, string name, string surname)
    {
        Id = id;

        Name = name;

        Surname = surname;
    }
    public abstract bool IsAuthorized();
}

class MajorCustomer : Customer
{
    public MajorCustomer(string id, string name, string surname):base(id, name, surname)
    {
            
    }
    public override bool IsAuthorized()
    {
        return true;
    }
}

class MinorCustomer : Customer
{
    public MinorCustomer(string id, string name, string surname) : base(id, name, surname)
    {

    }
    public override bool IsAuthorized()
    {
        return false;
    }
}

class Factory
{
    public static Customer CreateCustomer(int age, string id, string name, string surname)
    {
        if (age < 18)
        {
            return new MinorCustomer(id, name, surname);
        }
        else
        {
            return new MajorCustomer(id, name, surname);
        }
    }
}






class Program
{
    static void Main(string[] args)
    {
        Customer customer = Factory.CreateCustomer(21, "15612342", "Bob", "Snow");

        if(customer.IsAuthorized())
        {
            Console.WriteLine("Customer is authorized");
        }
        else
        {
            Console.WriteLine("Customer is not authorized");
        }
    }
}
Related