Jatin
Jatin

Reputation: 31744

Pattern Matching like behavior

I am at a lookout for a design pattern for my simple problem. Here is a simplified version.

class Animal{...}
class Dog extends Animal{...}
class Cat extends Animal{...}
... // so on, 3 other classes as of now

I have a static method (in reality exposed via web-service but its synonymous) which takes an id and returns an animal.

If a cat is returned then the other team using the cat object generates a CatReport. If Dog, then dog report (They can use it for anything). Obviously Cat & Dog have different attributes. Cat and Dog don't have anything else in common apart from the fact that they are Animals. So making a call like below, is insufficient because I need the precise type:

public static Animal getAnimal(int id){}

Not sufficient because animal does not contain all the information what the precise type can give me.

What is the best way to deal with this problem?


PS: In Scala, I would simply do pattern-matching on the object. This solves the problem elegantly.

One solution I have is: make a call which returns an enum signifying what the id corresponds to. And then have a separate call for each:

public static AnimalType getAnimalType(int id){}
public static Cat getCat(int id){}
public static Dog getDog(int id){}
....

But this is cumbersome.

Upvotes: 4

Views: 133

Answers (3)

alifirat
alifirat

Reputation: 2937

In a language like Java, you can simulate pattern matching behavior using the Visitor pattern.

You can do it in some steps :

  1. Define an interface Animal representing an Animal with a accept method
  2. Add some subclasses to Animal and give the same implementation like in my little example below.
  3. Define an interface Visitor and give it an implementation. This classe will allows to you to simulate some pattern matching on your classes.

Here a little example :

public interface Animal {
    public void accept(AnimalVisitor v); 
}

public class Dog extends Animal {
    public void accept(AnimalVisitor v) {
        v.visit(this);
    }
}

public class Cat extends Animal {
    public void accdept(AnimalVistior v) {
        v.visit(this);
    }   
}

public interface AnimalVisitor {
    public void visit(Dog d);
    public void visit(Cat c);
}

public class PrintAnimal implements AnimalVisitor {

    public void visit(Dog d) {
        System.out.println("Dog");
    }

    public void visit(Cat c) {
        System.out.println("Cat");
    }

}

Visitor pattern is a elegant way to solve your problem and also it's avoid the accumulation of if (x instance of bar) in one function. With this pattern, your code will be more readable and easier to extend.

The corresponding Scala code to make an idea of my answer:

abstract class Animal {}

case class Dog() extends Animal
case class Cat() extends Animal 

object Animal {

  def printAnimal(a : Animal) = a match {
    case x : Dog => "Dog"
    case x : Cat => "Cat"
    case _ => "Unknown"
  }
  def main(args : Array[String]) = {
    println(printAnimal(Dog()))
  }
}

Upvotes: 2

rahimv
rahimv

Reputation: 581

If I understand the question correctly, you want to call correct implementation of methods regards of the type of object you have. So if an animal is a cat, generate report method should be called from Cat class if you have code like below

public static Animal getAnimal(int id){ 
 //your code to return object of either Cat or Dog
}
animal.generateReport();

First of all as you said,

Obviously Cat & Dog have different attributes. Cat and Dog dont have anything else in common apart from the fact that they are Animals.

As the subclasses do not have any common functionality, define Animal as an interface instead of a class like given below

interface Animal{
 public void generateReport();
}

And create Cat and Dog like this

class Cat implements Animal{
//define cat specific attributes here
 public void generateReport(){
   //your logic to generate cat report
 }

}

class Dog implements Animal{
 //define dog specific attributes here
 public void generateReport(){
   //your logic to generate dog report
 }

}

Since the generateReport() method is defined in interface, all the classes implementing the interface must have generateReport().

So when you make a call like this,

public static Animal getAnimal(int id){ 
 //your code to return object of either Cat or Dog
}
animal.generateReport();

the underlying object's method will be called.

If you simply want to know what animal object refers to (returned from getAnimal method i.e. either Cat or Dog), you can check it like below

class Animal{

}
class Dog extends Animal{
     public String dogName = "Dog1";
}

class Cat extends Animal{
    public String catName = "Cat1";
}


public class HelloWorld{

     public static void main(String []args){

        //call getAnimal and get the object instead of following line
        Animal animal = new Cat();

        if ( animal instanceof Cat ){

            //cast Animal to Cat
            Cat cat = (Cat) animal;
            System.out.println(cat.catName);

        }else if ( animal instanceof Dog ){

            //cast Animal to Dog
            Dog dog = (Dog) animal;
            System.out.println(dog.dogName);
        }

     }

 }

Upvotes: -1

C.Champagne
C.Champagne

Reputation: 5489

Well, I don't see any really elegant solution but you can create a kind of report factory with this kind of code

public Report getCorrespondingReport(Animal animal){
    if(animal instanceof Dog) return new DogReport();
    if(animal instanceof Cat) return new CatReport();
...

...or you could make a generic report and use reflection to inspect your Animal instance and generate your report following general rules but it might not be doable.

Upvotes: 0

Related Questions