2

I was given 2 classes for the assignment: Sale and DiscountSale (which extends Sale). I'm supposed to create a new class called MultiItemSale which will create an array (shopping cart) of both Sale and DiscountSale objects. But I can't get methods from DiscountSale to work on DiscountSale ojbects within the array.

class Sale (the base class) has some methods, setName() and setPrice() in particular.

class DiscountSale extends Sale, so it can use setName() and setPrice(), but it also has setDiscount() among other things.

in MultiItemSale:

Sale[] shoppingCart = new Sale[numOfItems];

From my understanding, since DiscountSale extends Sale, both Sale and Discount Sale objects should be able to placed within this array, correct?

I use a for loop to ask if an item is discounted. If it isn't then:

shoppingCart[i] = new Sale();

if it is discounted:

shoppingCart[i] = new DiscountSale();

And this is where I begin to run into issues:

The following works, because setName() and setPrice() are from the Sale class

Also, this is all under an if-statement that says if item is discounted, then:

shoppingCart[i] = new DiscountSale();
shoppingCart[i].setName(name);
shoppingCart[i].setPrice(price);

But if I try to do this I get errors because setDiscount() is from DiscountSale:

shoppingCart[i].setDiscount(discount);

Eclipse tells me, "The method setDiscount(double) is undefined for the type Sale". If

shoppingCart[i] = new DiscountSale();

why can't I use a method from DiscountSale on that object? I think I have a misunderstanding of how polymorphism and arrays work.

Shane T
  • 123
  • 2
  • Possible duplicate of http://stackoverflow.com/questions/2559854/accessing-subclass-members-from-a-superclass-pointer-c – cxw Sep 13 '15 at 14:54

2 Answers2

2

All the compiler knows is that you have Sales, so those are the only methods you can use. Polymorphism is for the situation when you have the same function calls on different kinds of objects and the same function call will behave differently depending on what kind of object it is.

In this example, name, price, and discount could be parameters of your constructor. That way you could pass them when you say new, and you wouldn't have to call a setting method afterwards.

Edit: Looking at the Wikipedia article on the Circle-Ellipse Problem, it occurs to me that any Sale has an implicit discount of 0%. You could use only DiscountSales and be able to access all their members, perhaps including a flag indicating whether the discount can be changed.

cxw
  • 16,685
  • 2
  • 45
  • 81
  • Thanks for the answer. I thought about doing that too and it's a good solution, but the only problem is that later on in the project I have to make it so that the user can go back and edit some of the details of the individual items in their cart. So say the user put the discount as 20%, I need to make it so that they could say that it's actually only 15%, and it would be awesome to just be able to run `shoppingCart[i].setDiscount(newDiscount)`. I suppose I could just create a new object if they ever wanted to edit the details of a specific item. – Shane T Sep 13 '15 at 04:37
1

In the parlance of object-oriented design, a DiscountSale is a Sale.

Normally, a program would collect Sale and DiscoutSale objects into the same Sale collection in order to perform common Sale processing on those objects in the collection defined by the Sale API. So it doesn't make a lot of sense that you would try to use a statement like:

Sale[] shoppingCart = new Sale[] { ... };
    :
    :
shoppingCart[i].setDiscount(...);

Because a Sale isn't a DiscountSale, setting a discount rate on a top level Sale item, as you've designed it, isn't part of its API and the statement doesn't make a lot of sense.

DiscountSale objects will inherit the methods from Sale (a DiscountSale is a Sale), but Sale objects will have no knowledge of any subclass-specific methods defined in DiscountSale (a Sale object doesn't have to be a DiscountSale).

So what to do?

A common solution to a problem like this is to take advantage of polymorphism. For example, in your Sale class make a method such as getNetPrice() part of its API.

public class Sale {

    protected double price;
    :
    :
    public double getPrice() { return price; } // return original price

    public double getNetPrice() { return price; } // return discounted price
    :
    :
}

Notice that the two methods in Sale are identical. How come? Because their contracts are different. The first returns the original price. The second is specified to return the discounted price. Because Sale objects have no discount, it simply returns the original price as well.

However, the DiscountSale class would take advantage of polymorphism to fulfill the API contract:

public class DiscountSale extends Sale {

    private double discountRate; // specified as percent off (0.0 to 1.0)
    :
    :

    @Override
    public double getNetPrice() { 
        return (super.price - (super.price * discountRate)); 
    }
    :
    :
}

Here, DiscountSale objects are defined so that the discount rate is set at the time the object is created. This is pretty straightforward since, as we've said, setting a discount rate on Sale objects in the collection (which may or may not be discounted) is more confusing.

The DiscountSale uses the Sale contract and overrides the getNetPrice() method to calculate and return the discounted price. In this way, both objects can be in the same collection and share a common API to get at the nondiscounted or discounted prices.

scottb
  • 9,908
  • 3
  • 40
  • 56
  • Thanks for that. I wanted to make my MultiItemSale class without changing anything in the Sale and DiscountSale classes, but the question never said I couldn't edit the two given classes, so maybe that would have been a good idea. – Shane T Sep 14 '15 at 20:15