Search code examples
javagenericsbounding

Bounding Multiple Classes in Java


I am working on an assignment, and required is that I create two generic collections (called ComputerOrder and PartyTrayOrder) of several classes: ComputerPart, Peripheral, and Service, and and of Cheese, Fruit, and Service respectively. All of these classes (ComputerPart, Fruit, Service, etc) extend one class called Product.

The problem I have encountered is that it seems to be impossible to bound more than one class in Java, as you may only bound multiple interfaces, and I am not allowed to modify the existing class structure.

I've read around and seen it suggested that I create and interface that "codes in" all of the relevant classes, but I fail to see how this is possible without ComputerPart, Peripheral, etc ALSO being interfaces and then using the extend keyword. I've seen it further suggested that I create abstract classes called ComputerProduct and PartyTrayProduct, but I fail to see how this will in any way restrict the types.

I am not allowed to post images, but here is a class diagram, including some unrelated assignment objects

Here is my current code for ComputerOrder, though it only bounds the parent class product, meaning it isn't really as the assignment specifies.

public class ComputerOrder<T extends Product> extends GenericOrder<T> {
    public ComputerOrder() {
        super();
    }
}

Here are the assignment specifications:

  1. Design and implement a generic container called GenericOrder that acts as a collection of an arbitrary number of objects in Products.java. Design a mechanism that gives each instance of the container a unique identifier. You must use Java generics features.

  2. Design and implement a subclass of GenericOrder called ComputerOrder that takes an arbitrary number of different classes of ComputerPart objects, Peripheral objects, and Service objects. Implement as many methods as necessary.

  3. Design and implement a subclass of GenericOrder called PartyTrayOrder that takes an arbitrary number of different classes of Cheese objects, Fruit objects and Service objects. Implement as many methods as necessary. (...)


Solution

  • Here's the only solution that I came up with, which follows the assignment to the letter and still makes some sense (even though I think that it is not a "good" solution - see the notes below): The ComputerOrder and PartyTrayOrder could offer methods that only accept the specialized types of Product:

    abstract class Product {}
    class ComputerPart extends Product {}
    class Peripheral extends Product { }
    class Cheese extends Product {} 
    class Fruit extends Product {}
    class Service extends Product {} 
    
    abstract class GenericOrder<T extends Product> {
        protected final void addImpl(T t) {
        }
    }
    
    class ComputerOrder extends GenericOrder<Product> {
        void add(ComputerPart t) {
            addImpl(t);
        }
        void add(Peripheral t) {
            addImpl(t);
        }
        void add(Service t) {
            addImpl(t);
        }
    }
    
    class PartyTrayOrder  extends GenericOrder<Product> {
        void add(Cheese t) {
            addImpl(t);
        }
        void add(Fruit t) {
            addImpl(t);
        }
        void add(Service t) {
            addImpl(t);
        }
    }
    

    This way, the order implementations could exactly accept the right types:

    public class ProductExample {
    
        public static void main(String[] args) {
            ComputerOrder c = new ComputerOrder();
            c.add(new ComputerPart());
            c.add(new Peripheral());
            //c.add(new Cheese()); // Not allowed
            //c.add(new Fruit()); // Not allowed
            c.add(new Service());
    
            PartyTrayOrder p = new PartyTrayOrder();
            //p.add(new ComputerPart());  // Not allowed
            //p.add(new Peripheral());  // Not allowed
            p.add(new Cheese());
            p.add(new Fruit());
            p.add(new Service());
    
        }
    }
    

    I assume that this is the intended solution, because the assignment contains the broad hint:

    Implement as many methods as necessary.

    So most likely, the goal is not to implement one method that is magically bounded for multiple classes. Instead, one method for each "category" has to be added.


    An aside: My gut feeling is that this design could be improved. Just imagine you'd have to create classes CarOrder and FahsionOrder etc. for new types of orders. Or imagine the PartyTrayOrder would have to be extended to also handle classes like Meat or Dip or Salad: You'd end up with dozens of classes with dozens of specialized methods.

    This could all be avoided by introducing a dedicated "product type" that exactly matches the types that are acceptable for specific "order type". So I think that (Productshould be an interface to begin with, and) there should be interfaces like ComputerProduct and PartyTrayProduct, as in

    interface Product {}
    
    interface ComputerProduct extends Product {}
    class ComputerPart implements ComputerProduct  {}
    class Peripheral implements ComputerProduct  {}
    
    interface PartyTrayProduct extends Product {}
    class Cheese implements PartyTrayProduct{} 
    class Fruit implements PartyTrayProduct{}
    
    class Service implements Product {} 
    class DeliveryService implements PartyTrayProduct{} 
    class AssemblyService implements ComputerProduct  {}
    

    That way, the required bounds for the specific ComputerOrder and PartyTrayOrder are already modeled with the class hierarchy. The advantage then is: You don't need the ComputerOrder and PartyTrayOrder classes any more! The GenericOrder could then be non-abstract, and in order to create the specific types of orders you'd just properly use the generic type bound.

    Here is a complete example, where I've just thrown in Salad as a new PartyTrayProduct, and CarPart as a new type of product, without having to extend or modify any of the "infrastructure classes":

    interface Product {}
    
    interface ComputerProduct extends Product {}
    class ComputerPart implements ComputerProduct  {}
    class Peripheral implements ComputerProduct  {}
    
    interface PartyTrayProduct extends Product {}
    class Cheese implements PartyTrayProduct{} 
    class Fruit implements PartyTrayProduct{}
    
    class Service implements Product {} 
    class DeliveryService implements PartyTrayProduct{} 
    class AssemblyService implements ComputerProduct  {}
    
    class Salad implements PartyTrayProduct{} // A new PartyTrayProduct
    
    // Entirely new product type:
    interface CarProduct extends Product {}
    class CarPart implements CarProduct  {}
    class CarInterior implements CarProduct  {}
    
    class GenericOrder<T extends Product> {
        public void add(T t) { }
    }
    
    public class ProductExample2 {
        public static void main(String[] args) {
    
            GenericOrder<ComputerProduct> c = new GenericOrder<ComputerProduct>();
            c.add(new ComputerPart());
            c.add(new Peripheral());
            //c.add(new Cheese()); // Not allowed
            //c.add(new Fruit()); // Not allowed
            c.add(new AssemblyService());
    
            GenericOrder<PartyTrayProduct> p = new GenericOrder<PartyTrayProduct>();
            //p.add(new ComputerPart());  // Not allowed
            //p.add(new Peripheral());  // Not allowed
            p.add(new Cheese());
            p.add(new Fruit());
            p.add(new Salad()); // Just add it...
            p.add(new DeliveryService());
    
            // Easy to extend:
            GenericOrder<CarProduct> e = new GenericOrder<CarProduct>();
            e.add(new CarPart());
            e.add(new CarInterior());
        }
    }