Intuition vs Design principles - java

I have a class hierarchy like this Beverage -> Coffee-> Latte.
Where Beverage is the abstract superclass being extended by Coffee. Coffee class then adds some behavior but is also abstract. Latte extends Coffee class and is a concrete class. I have used inheritance to add behaviors here. And inheritance do have drawbacks like the visibility of superclass methods, making code fragile, the code is tightly coupled. So, programming principles dictate Composition should be preferred over Inheritance. But in this case inheritance feels so natural as Latte is a type of Coffee and Coffee is a type of Beverage that using composition to add behavior feels wrong in spite of its benefits. So the question here is
Should Intuition override Design principles?
Beverage:
public abstract class Beverage {
private final String name;
private final double price;
Beverage(String name, double price){
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public double getPrice() {
return price;
}
public abstract void make();
}
Coffee:
public abstract class Coffee extends Beverage {
public Coffee(String name, double price) {
super(name, price);
}
public final void make(){
grindBeans();
takeShot();
frothMilk();
addCondiments();
}
public void grindBeans(){
System.out.println("Grinding Beans...");
}
public void takeShot(){
System.out.println("Taking Shot....");
}
public abstract void frothMilk();
public abstract void addCondiments();
}
Latte:
public class Latte extends Coffee {
public Latte() {
super("Latte", 4.0);
}
#Override
public void frothMilk() {
System.out.println("Frothing milk to create micro foam");
}
#Override
public void addCondiments() {
// TODO Auto-generated method stub
}
}
EDIT: Adding Sugar to existing structure. Only new code is shown.
public abstract class Beverage {
private Sugar sugar;
public Sugar getSugar() {
return sugar;
}
public void setSugar(Sugar sugar) {
this.sugar = sugar;
}
}
Coffee:
public abstract class Coffee extends Beverage {
public final void make(){
grindBeans();
takeShot();
frothMilk();
addSugar();
addCondiments();
}
public void addSugar(){
Sugar sugar = super.getSugar();
if(!(sugar instanceof NoSugar)){
System.out.println("adding " + sugar.getTeaspoon() + " teaspoon sugar");
}
}

While composition has many benefits over inheritance, there's nothing wrong with using inheritance where it feels natural (i.e. in a truly is-a relationship). If it's natural, go ahead and use it.

Composition means A has a B, and inheritance means A is kind of B. In your case you're 100% right - inheritance should be used: latee is a cofee and a cofee is a beverage.
Why do you consider it to be fragile? E.g., latee should have all the properties of cofee, but can implement them differently. Nothing fragile here - it's a polymorphism. If you want to restrict overriding of parent's methods - mark them final.
As an example of composition - there are Car and Wheel. Car has a wheel.
Car needs wheels for working, but they're completely different objects. Car can be opened, closed, started, etc.. - wheel can't.
Wheel can revolute and deflate. Car - can't.
P.S.:
Oh, I think I got what you mean by "fragile". Here is an article on it http://www.javaworld.com/article/2076814/core-java/inheritance-versus-composition--which-one-should-you-choose-.html
For me this "composition instead of inheritance" thing still looks like an OOP hack (especially, the example in the article: apple IS A fruit, no way around it :))
Whenever I will see this hack in use, I would probably think of an API designer to be improvident.

I think in the book head first design patterns the example they use is that the latte class can have several configurations. By using composition you can create custom classes at runtime rather than during design
You can only extend from one class as well which may be a limiting factor depending on what you are doing.

Yes, at first sight inheritance here seems all right going with the argument that wherever its naturally so it makes sense to have inheritance. Coffee is a beverage and latte is a coffee so inheritance is okay.
Though it makes sense, what if we have more types of coffees - cappuccino, frappe etc. Then do we make more sub-types? I think then we can use a decorator pattern where Latte extends coffee and will itself be a coffee and so on. In this case we will make use of composition and inheritance both together. So, twisting the argument a bit, in this case we need both Composition and Inheritance and not either/or.
Having said that( and if you bought what I said above), what if we keep on decorating this Latte say by having variants American Latte and French Latte. Then which is our base class for a decorator? Is it Coffee or Latte? If we choose Coffee then American Latte is composed of Latte which is composed of Coffee. Composition is pulling it all together. If, however, we say its Latte then we make another base decorator component i.e. Latte. Then we have 2 levels of decorators - one centered around coffee and another (1 level below) centered around Latte which makes it too confusing. So, it makes more sense to have Coffee as the abstract decorator and use composition to bring in Latte and then French/American latte. Then, composition has an edge over inheritance here.

If you are developing simple system where you have four subtypes of coffee then you should stay with inheritance.
Solution with inheritance have its problems. They will be visible especially when amount of coffee subtypes will increase.
First problem is with unimplemented methods. You will most likely have some methods which will just have empty implementation.
public class Espresso extends Coffee {
public Espresso() {
super("Espresso", 4.0);
}
#Override
public void frothMilk() {
// well I don't really need that method, so I will just write a comment
}
#Override
public void addCondiments() {
// that one is also unnecessary, does it mean that my inheritance tree is wrong?
}
}
Second problem will occur with exponential explosion of parameters in your coffees. If you decide to have Latte with sugar you might end up with special class for this Latte, LatteWithSugar, which will also create new *.WithSugar for each already existing class. The same with each special modification of class. Please note that amount of classes increases exponentially. If you have 8 kinds of coffee adding one parameter will make 16 classes which is suspicious.
It is a matter of scale.
For small app it might be overengineering. For big coffee shop application it will save you hours of maintenance.

Related

Good OO way to categorize related objects in a model in java

I have a class Vehicles with three list fields, namely:
turboVehicles : List<ITurboVehicle>,
trailerVehicle : List<ITrailerVehicle>,
vehicles : List<IVehicle>, (containing all vehicles from lists 1 and 2 and more)
where both ITurboVehicle and ITrailerVehicle extends IVehicle.
I want this class to be able to move all vehicles at the same time, and also be able to gas(), brake() them all at the same time as well. The thing is that ITurboVehicle and ITrailerVehicle are subinterfaces to IVehicle, and have further functionality that I want this class to be able to trigger, like turboOn() and Off.
Main question:
What is a good way of modeling these lists of vehicles for appropriate use in client code? I want to hide as much complexity as possible and keep it organized well in an OO sense. The vehicles and trailerVehicles lists are both updated whenever addTrailerTruck(TrailerVehicle tv) is used for example, to distinguish between them when sub-class specific methods are used. I know that the visitor pattern could have been a good choice to achieve some of the functionality that I have been looking for, but I want to try and find other solutions.
Less important, side-stepping question:
The following has more to do with the general structure I have been trying out to make the Vehicles class easy and intuative to use: I have been trying to use the composite design pattern, but it seems like the composite class has to be of the same type as the components, which has gotten me stuck sort of. Are there any ways to use the composite pattern and still be able to access all the sub-class functionality?
In my view, it is better to have just one class which manages of state of all vehicles. After doing this, your classes will correspond to the single responsibility principle of SOLID. Read more about single responsibility principle of SOLID here
Let me show an example:
public interface IVehicle
{
void Gas();
void Brake();
}
public interface ITurboVehicle : IVehicle
{ }
public interface ITrailerVehicle : IVehicle
{ }
and concrete implementation of vehicle:
public class TrailerVehicle : ITrailerVehicle
{
public void Brake()
{
throw new NotImplementedException();
}
public void Gas()
{
throw new NotImplementedException();
}
}
And vehicle manager class:
public class VehicleManager
{
public List<IVehicle> Vehicles { get; set; } = new List<IVehicle>();
public void AddTrailer(ITrailerVehicle trailerVehicle)
{
Vehicles.Add(trailerVehicle);
}
public void GasAll()
{
foreach (IVehicle vehicle in Vehicles)
vehicle.Gas();
}
}

What is the point of a “sealed interface” in Java?

Sealed classes and sealed interfaces were a preview feature in Java 15, with a second preview in Java 16, and now proposed delivery in Java 17.
They have provided classic examples like Shape -> Circle, Rectangle, etc.
I understand sealed classes: the switch statement example provided makes sense to me. But, sealed interfaces are a mystery to me. Any class implementing an interface is forced to provide definitions for them. Interfaces don't compromise the integrity of the implementation because the interface is stateless on its own. Doesn't matter whether I wanted to limit implementation to a few selected classes.
Could you tell me the proper use case of sealed interfaces in Java 15+?
Basically to give a sealed hierarchy when there is no concrete state to share across the different members. That's the major difference between implementing an interface and extending a class - interfaces don't have fields or constructors of their own.
But in a way, that isn't the important question. The real issue is why you would want a sealed hierarchy to begin with. Once that is established it should be clearer where sealed interfaces fit in.
(apologies in advance for the contrived-ness of examples and the long winded-ness)
1. To use subclassing without "designing for subclassing".
Lets say you have a class like this, and it is in a library you already published.
public final class Airport {
private List<String> peopleBooked;
public Airport() {
this.peopleBooked = new ArrayList<>();
}
public void bookPerson(String name) {
this.peopleBooked.add(name);
}
public void bookPeople(String... names) {
for (String name : names) {
this.bookPerson(name);
}
}
public int peopleBooked() {
return this.peopleBooked.size();
}
}
Now, you want to add a new version to your library that will print out the names of people booked as they are booked. There are several possible paths to do this.
If you were designing from scratch, you could reasonably replace the Airport class with an Airport interface and design the PrintingAirport to compose with a BasicAirport like so.
public interface Airport {
void bookPerson(String name);
void bookPeople(String... names);
int peopleBooked();
}
public final class BasicAirport implements Airport {
private final List<String> peopleBooked;
public Airport() {
this.peopleBooked = new ArrayList<>();
}
#Override
public void bookPerson(String name) {
this.peopleBooked.add(name);
}
#Override
public void bookPeople(String... names) {
for (String name : names) {
this.bookPerson(name);
}
}
#Override
public int peopleBooked() {
return this.peopleBooked.size();
}
}
public final class PrintingAirport implements Airport {
private final Airport delegateTo;
public PrintingAirport(Airport delegateTo) {
this.delegateTo = delegateTo;
}
#Override
public void bookPerson(String name) {
System.out.println(name);
this.delegateTo.bookPerson(name);
}
#Override
public void bookPeople(String... names) {
for (String name : names) {
System.out.println(name);
}
this.delegateTo.bookPeople(names);
}
#Override
public int peopleBooked() {
return this.peopleBooked.size();
}
}
This isn't doable in our hypothetical though because the Airport class already exists. There are going to be calls to new Airport() and methods that expect something of type Airport specifically that can't be kept in a backwards compatible way unless we use inheritance.
So to do that pre-java 15 you would remove the final from your class and write the subclass.
public class Airport {
private List<String> peopleBooked;
public Airport() {
this.peopleBooked = new ArrayList<>();
}
public void bookPerson(String name) {
this.peopleBooked.add(name);
}
public void bookPeople(String... names) {
for (String name : names) {
this.bookPerson(name);
}
}
public int peopleBooked() {
return this.peopleBooked.size();
}
}
public final class PrintingAirport extends Airport {
#Override
public void bookPerson(String name) {
System.out.println(name);
super.bookPerson(name);
}
}
At which point we run into one of the most basic issues with inheritance - there are tons of ways to "break encapsulation". Because the bookPeople method in Airport happens to call this.bookPerson internally, our PrintingAirport class works as designed, because its new bookPerson method will end up being called once for every person.
But if the Airport class were changed to this,
public class Airport {
private List<String> peopleBooked;
public Airport() {
this.peopleBooked = new ArrayList<>();
}
public void bookPerson(String name) {
this.peopleBooked.add(name);
}
public void bookPeople(String... names) {
for (String name : names) {
this.peopleBooked.add(name);
}
}
public int peopleBooked() {
return this.peopleBooked.size();
}
}
then the PrintingAirport subclass won't behave correctly unless it also overrided bookPeople. Make the reverse change and it won't behave correctly unless it didn't override bookPeople.
This isn't the end of the world or anything, its just something that needs to be considered and documented - "how do you extend this class and what are you allowed to override", but when you have a public class open to extension anyone can extend it.
If you skip documenting how to subclass or don't document enough its easy to end up in a situation where code you don't control that uses your library or module can depend on a small detail of a superclass that you are now stuck with.
Sealed classes let you side step this by opening your superclass up to extension only for the classes you want to.
public sealed class Airport permits PrintingAirport {
// ...
}
And now you don't need to document anything to outside consumers, just yourself.
So how do interfaces fit in to this? Well, lets say you did think ahead and you have the system where you are adding features via composition.
public interface Airport {
// ...
}
public final class BasicAirport implements Airport {
// ...
}
public final class PrintingAirport implements Airport {
// ...
}
You might not be sure that you don't want to use inheritance later to save some duplication between the classes, but because your Airport interface is public you would need to make some intermediate abstract class or something similar.
You can be defensive and say "you know what, until I have a better idea of where I want this API to go I am going to be the only one able to make implementations of the interface".
public sealed interface Airport permits BasicAirport, PrintingAirport {
// ...
}
public final class BasicAirport implements Airport {
// ...
}
public final class PrintingAirport implements Airport {
// ...
}
2. To represent data "cases" that have different shapes.
Lets say you send a request to a web service and it is going to return one of two things in JSON.
{
"color": "red",
"scaryness": 10,
"boldness": 5
}
{
"color": "blue",
"favorite_god": "Poseidon"
}
Somewhat contrived, sure, but you can easily imagine a "type" field or similar that distinguishes what other fields will be present.
Because this is Java, we are going to want to map the raw untyped JSON representation into classes. Lets play out this situation.
One way is to have one class that contains all the possible fields and just have some be null depending.
public enum SillyColor {
RED, BLUE
}
public final class SillyResponse {
private final SillyColor color;
private final Integer scaryness;
private final Integer boldness;
private final String favoriteGod;
private SillyResponse(
SillyColor color,
Integer scaryness,
Integer boldness,
String favoriteGod
) {
this.color = color;
this.scaryness = scaryness;
this.boldness = boldness;
this.favoriteGod = favoriteGod;
}
public static SillyResponse red(int scaryness, int boldness) {
return new SillyResponse(SillyColor.RED, scaryness, boldness, null);
}
public static SillyResponse blue(String favoriteGod) {
return new SillyResponse(SillyColor.BLUE, null, null, favoriteGod);
}
// accessors, toString, equals, hashCode
}
While this technically works in that it does contain all the data, there isn't all that much gained in terms of type-level safety. Any code that gets a SillyResponse needs to know to check the color itself before accessing any other properties of the object and it needs to know which ones are safe to get.
We can at least make the color an enum instead of a string so that code shouldn't need to handle any other colors, but its still far less than ideal. It gets even worse the more complicated or more numerous the different cases become.
What we ideally want to do is have some common supertype to all the cases that you can switch on.
Because its no longer going to be needed to switch on, the color property won't be strictly necessary but depending on personal taste you can keep that as something accessible on the interface.
public interface SillyResponse {
SillyColor color();
}
Now the two subclasses will have different sets of methods, and code that gets either one can use instanceof to figure out which they have.
public final class Red implements SillyResponse {
private final int scaryness;
private final int boldness;
#Override
public SillyColor color() {
return SillyColor.RED;
}
// constructor, accessors, toString, equals, hashCode
}
public final class Blue implements SillyResponse {
private final String favoriteGod;
#Override
public SillyColor color() {
return SillyColor.BLUE;
}
// constructor, accessors, toString, equals, hashCode
}
The issue is that, because SillyResponse is a public interface, anyone can implement it and Red and Blue aren't necessarily the only subclasses that can exist.
if (resp instanceof Red) {
// ... access things only on red ...
}
else if (resp instanceof Blue) {
// ... access things only on blue ...
}
else {
throw new RuntimeException("oh no");
}
Which means this "oh no" case can always happen.
An aside: Before java 15 to remedy this people used the "type safe visitor" pattern. I recommend not learning that for your sanity, but if you are curious you can look at code ANTLR generates - its all a large hierarchy of differently "shaped" data structures.
Sealed classes let you say "hey, these are the only cases that matter."
public sealed interface SillyResponse permits Red, Blue {
SillyColor color();
}
And even if the cases share zero methods, the interface can function just as well as a "marker type", and still give you a type to write when you expect one of the cases.
public sealed interface SillyResponse permits Red, Blue {
}
At which point you might start to see the resemblance to enums.
public enum Color { Red, Blue }
enums say "these two instances are the only two possibilities." They can have some methods and fields to them.
public enum Color {
Red("red"),
Blue("blue");
private final String name;
private Color(String name) {
this.name = name;
}
public String name() {
return this.name;
}
}
But all instances need to have the same methods and the same fields and those values need to be constants. In a sealed hierarchy you get the same "these are the only two cases" guarantee, but the different cases can have non-constant data and different data from each other - if that makes sense.
The whole pattern of "sealed interface + 2 or more record classes" is fairly close to what is intended by constructs like rust's enums.
This also applies equally to general objects that have different "shapes" of behaviors, but they don't get their own bullet point.
3. To force an invariant
There are some invariants, like immutability, that are impossible to guarantee if you allow subclasses.
// All apples should be immutable!
public interface Apple {
String color();
}
public class GrannySmith implements Apple {
public String color; // granny, no!
public String color() {
return this.color;
}
}
And those invariants might be relied upon later on in the code, like when giving an object to another thread or similar. Making the hierarchy sealed means you can document and guarantee stronger invariants than if you allowed arbitrary subclassing.
To cap off
Sealed interfaces more or less serve the same purpose as sealed classes, you just only use concrete inheritance when you want to share implementation between classes that goes beyond what something like default methods can give.
Although interfaces have no state themselves, they have access to state, eg via getters, and may have code that does something with that state via default methods.
Therefore the reasoning supporting sealed for classes may also be applied to interfaces.
Suppose you write an authentication library, containing an interface for password encoding, ie char[] encryptPassword(char[] pw). Your library provides a couple of implementations the user can choose from.
You don't want him to be able to pass in his own implementation that might be insecure.
Could you tell me the proper use case of sealed interfaces in Java
15+?
I wrote some experimental code and a supporting blog to illustrate how sealed interfaces could be used to implement an ImmutableCollection interface hierarchy for Java that provides contractual, structural and verifiable immutability. I think this could be a practical use case for sealed interfaces.
The example includes four sealed interfaces: ImmutableCollection, ImmutableSet, ImmutableList and ImmutableBag. ImmutableCollection is extended by ImmutableList/Set/Bag. Each of the leaf interfaces permits two final concrete implementations. This blog describes the design goal of restricting the interfaces so developers cannot implement "Immutable" interfaces and provide implementations that are mutable.
Note: I am a committer for Eclipse Collections.
Interfaces are not always entirely defined by their API alone. Take, for example ProtocolFamily. This interface would be easy to implement, considering its methods, but the result would not be useful regarding the intended semantics, as all methods accepting ProtocolFamily as input would just throw UnsupportedOperationException, in the best case.
This is a typical example for an interface that would be sealed if that feature existed in earlier versions; the interface is intended to abstract the implementations exported by a library, but not to have implementations outside that library.
The newer type ConstantDesc mentions that intention even explicitly:
Non-platform classes should not implement ConstantDesc directly. Instead, they should extend DynamicConstantDesc…
API Note:
In the future, if the Java language permits, ConstantDesc may become a sealed interface, which would prohibit subclassing except by explicitly permitted types.
Regarding possible use cases, there is no difference between a sealed abstract class and a sealed interface, but the sealed interface still allows implementors extending different classes (within the limits set by the author). Or being implemented by enum types.
In short, sometimes, interfaces are used to have the least coupling between a library and its clients, without the intention of having client-side implementations of it.
Since Java introduced records in version 14, one use case for sealed interfaces will certainly be to create sealed records. This is not possible with sealed classes, because records cannot extend a class (much like enums).
Before java 15 developers used to think in a way that code reusability is the goal. But it's not true to all extents, in some cases we want wide accessibility but not extensibility for better security and also codebase management.
This feature is about enabling more fine-grained inheritance control in Java. Sealing allows classes and interfaces to define their permitted subtypes.
The sealed interface allows us to enable it to reason clearly all the classes that can implement it.

Multiple implementations of common interface

I am creating an interface say 'Car'
public interface Car {
public void drive(int Speed); // for cars which do not have gears
}
which has multiple implementation like Bus, Truck and etc
Then in a main class called Trafic, the drive method of all the implementaions should be called (the order does not matter).
I have two option for Traffic Class design:
1) either use a property for every Car implementation which makes the consrtuctor hard
to maintain and ugly (with many arguments of same type and the fact that by adding new Car implementation it should be changed) like this:
public class Traffic {
private Car bus;
private Car truck;
...
public Traffic(Car bus, Car truck,...){
this.bus = bus;
this.truck = truck;
...
}
public void run(){
bus.drive();
truck.drive();
...
}
}
2) or pass a list of Car's to Traffic. Then what if i want to check something else about the Bus, so sometimes i need to find an implementation with Instance Of. It would be something like this:
public class Traffic {
private List<Car> cars;
public Traffic(Car bus, Car truck,...){
this.bus = bus;
this.truck = truck;
...
}
public void run(){
for(Car car : cars){
car.drive();
}
}
}
I feel kind of unsatisfied with either of these solutions. Is there any other solution?
what if i want to check something else about the Bus
In polymorphic design you should create a functionality delegating method that is called on the higher level and put the implementation specific logic forking there. Sometimes this means creating a whole mini-DI helper class to provide extra services to the polymorphic implementations of parent class which can make things look a bit weird but at the same time there really isn't a way around it when in OOP.
Consider the following class hierarchy:
public abstract class Vehicle {
public abstract void drive();
}
public abstract class Car extends Vehicle {
public void drive() {
System.out.println("Driving around...");
}
public void honkHorn() {...}
}
public final class Bus extends Car {
}
public final class Truck extends Car {
#Override
public void drive() {
super.drive();
honkHorn();
}
}
Here I have augmented your class hierachy with two additional features:
There is now a common super class Vehicle because maybe you want to expand to boats which are also driven but definitely not in the same way as cars.
Truck honks its horn every time it is also driven
The latter part is the important one here; one of the bigger benefits of polymorphism is introducing extra functionality like this in subclasses. If external dependencies exist and state checking is required - for example we really don't want to drive our boats on highways - you can introduce a helper on top level to provide the implementations with extra details. So, changing Vehicle like this...
public abstract class Vehicle {
public abstract void drive(Surface surface);
}
now allows us to define a Boat...
public abstract class Boat extends Vehicle {
#Override
public void drive(Surface surface) {
if (surface.isWater()) {
doDrive(surface);
}
}
// this is needed to ensure that extending classes really do implement the driving capability!
public abstract void doDrive(Surface surface);
}
which limits the applicability of all Boat implementations to only watery surfaces.
To bring all this together with your Traffic class you can now start to consider what is the proper object hierarchy for dealing with various aspects of the class modeling given here. You could for instance define that Traffic happens on Surface and the class handles only moving instances of Vehicles on Lanes and the logic would query for eg. vehicle dimensions and speed to consider such cases as when a bus can switch lanes after picking up passengers from a stop or if the lane is already filled to brim and is moving too slow to warrant a lane change at all.
To answer your question, internally you'll probably want to use a List<Vehicle> in any case as that provides ordering for your vehicles, but the Traffic class should not take in a list of vehicles as in case where Traffic represents a highway you really don't join the traffic from random spots but from specific intersections/junctions which insert the incoming vehicle to specific spot in relation to the highway. So, you probably want to create a few methods for inserting/removing Vehicles into and out from Traffic at specific points. In this scenario the internal list probably should contain wrapper objects to augment the vehicle info with the relative position of the vehicle on the lane to make inserting new vehicles easier and in general to update all the positions in one easy loop, but this post is now getting really long and I'm assuming a lot here... :)
You could use a Varargs as argument and you should use generics in your collection it is safer.
public class Traffic {
private List<Car> cars;
...
public Traffic(Car... cars){
this.cars = Arrays.asList(cars);
}
...
}
If you want to have a List which has not the limitations of the list returned by Arrays.asList() you could do that :
public class Traffic {
private List<Car> cars;
...
public Traffic(Car... cars){
this.cars = new ArrayList<>();
this.cars.addAll(Arrays.asList(cars));
}
...
}

Design for a shop - multiple product classes

I was reading about Spring and encountered an example consisting of an abstract product class with name and price fields.
Next, there is a Battery class which extends the Product class and adds a rechargable field. Then, a CDDrive class (also) extending Product but adding a capacity field.
In the real world when we often have products having many disparate attributes, how does one model arbitrary products with arbitrary properties and fields?
Does having a class for each product make sense?
So, can you guys please suggest a pattern for achieving this?
Thanks,
Ouney
Good question. We had a similar situation before where we had GUI components that shared many of their abstract parent, but each page had its own set of labels that weren't shared by others. The standoff was on. We found it silly to just keep creating subclasses because of the mutually disjoint properties they had. What did it for us was maps. First, to have a subclass is to have one or more distinguishing properties that are fist class objects. Rechargeable for batteries and capacity for cd drives in your case. Then for the properties one can't think of at the time of building, or simply differ in minor naming conventions, use maps. I demonstrate with the example below.
The product:
public abstract class Product {
String name;
Double price;
Map<String, Object> propMap;
public Product(String name, Double price) {
this.name = name;
this.price = price;
propMap = new HashMap<>();
}
public void add2propMap(String key, Object value) {
propMap.put(key, value);
}
public String toString() {
return "Product [name=" + name + ", price=" + price + ", propMap=" + propMap + "]";
}
}
The CdDrive:
public class CdDrive extends Product {
String capacity;
public CdDrive(String name, Double price, String capacity) {
super(name, price);
this.capacity = capacity;
}
}
The Battery:
public class Battery extends Product {
Boolean rechargable;
public Battery(String name, Double price, Boolean rechargable) {
super(name, price);
this.rechargable = rechargable;
}
}
Then a client:
public class Client {
public static void main(String[] args) {
List<Product> productList = new ArrayList<>();
Battery energizer = new Battery("Energizer", 12d, true);
energizer.add2propMap("numInPackage", new Integer(8));
energizer.add2propMap("make", "US");
productList.add(energizer);
CdDrive superDrive = new CdDrive("Apple Drive", 200d, "200 GB");
superDrive.add2propMap("type", "External");
superDrive.add2propMap("expandable", false);
productList.add(superDrive);
productList.forEach(p -> System.out.println(p));
}
}
Which gives this when run:
Product [name=Energizer, price=12.0, propMap={numInPackage=8, make=US}]
Product [name=Apple Drive, price=200.0, propMap={expandable=false, type=External}]
This setup made the architecture scalable, maintainable and modifiable. The map keys always reported what was in there in case in doubt. Adding is easy and so is modifying.
Does having a class for each product make sense?
In real life situation, it rarely makes sense. They are just making up some example to make you get the feeling of it.
Just imagine your online shop sells CD players, now you want to add some MD players in your product list, and you need to change your code and redeploy the application just because of it. Non-sense huh?
Unless you have bunch of specific function for some specific type of product, having a dedicated class for such type of product will make sense. (e.g. Product, PhysicallyDeliverableProduct something like that. Still there are better way to design it though)
In real life, the way to solve the issue in your question, is mostly by designing your Product to keep some arbitrary properties (e.g. keeping a Map<String,Object>, so you can put ["rechargeable", true] for a battery you add on your site.
Design pattern? I think what you are looking for is still far from required to make use of patterns. Personally I will suggest you to take a look on the book "Analysis Pattern" by Martin Fowler. You may not be able to use the design in it directly, but it give you feel on what real life design looks like
Does having a class for each product make sense?
To me it absolutely makes sense to have separate classes for separate products.
That makes your code more loosely coupled. In future if you want to change the implementation of a particular product, changing the code won't mess up the implementation of other products if you have a separate class for that. The generic methods & properties you can put in an abstract class.
a pattern for achieving this?
You might want to look at the Factory & template pattern.
You can create an interface Product & all the classes will implement that interface & define their own implementations.
Use abstract class only when you want to provide a default behaviour to your methods. For an instance have a look at the template pattern here.
An abstract class game is created which defines the play method. initialize & startPlay etc can have their respective definition in the subclasses but the play method will always run the other methods.
public abstract class Game {
abstract void initialize();
abstract void startPlay();
abstract void endPlay();
//template method
public final void play(){
//initialize the game
initialize();
//start game
startPlay();
//end game
endPlay();
}
}
If you don't intend to provide any default behaviour rather just declare the properties & methods in an interface Product & let the classes implement that.
interface Product{
String NAME="defaultName";
Integer PRICE=5;
initialCost(); // example of a generic method
}
//Note that name & price if you declare those in interface will be treated as constants.
class Battery implements Product{
Boolean rechargable =false;
public void initialCost(){
//method definition
}
}
class CdDrive implements Product{
Integer capacity = xxxx;
public void initialCost(){
//CdDrive method definition
}
}
You can create the objects as
Product product = new Battery();
Product nextProduct = new CdDrive();
this makes your code loosely coupled. Also known as programming to an interface.

Abstract Inheritance and Generics (with playing cards)

So, I've been working on some playing cards in Java. (Not for any practical purpose really, I just enjoy playing cards and they're good practice) Now, right now, I'm making some Card Structures, decks, hands, piles, etc. They're all essentially the same, so I figure that I'd like to use some inheritance.
The problem that I've encountered is that the core of each structure is some type of collection, but they don't all use the same collection type. A deck uses a Stack, since a deck essentially functions as a stack most often. But, a hand uses an ArrayList (If there is something more efficient than an ArrayList to use, that would be good to know as well).
So, when trying to write an abstract class, I'd like to avoid using abstract methods (as it defeats the original purpose of making an abstract class, to conserve code). But, all of these methods rely on the core collection, for obvious reasons, but I don't know what type the collection is. This is what I've tried so far:
public abstract class CardSet
{
protected AbstractCollection<Card> elCollection;
public CardSet()
{
super();
}
public CardSet(Card[] cards)
{
super();
for(Card c: cards)
elCollection.add(c);
}
public void add(Card c)
{
elCollection.add(c);
}
}
public class Pair extends CardSet //Pair is just a dummy class to get inheritance right
{
ArrayList<Card> elPair;
public Pair()
{
elPair = new ArrayList<Card>(); //elPair is defined, because casting
elCollection = elPair; // elCollection to arraylist would be a pain.
}
public Pair(Card[] cards)
{ this();
super(cards);
}
}
First off, forgive my variable names. I used to name everything "theVariable", but I decided that using "el" was more interesting. (You've got to amuse yourself somehow, right?) Also, using protected was just for the sake of simplicity.
Now, the general idea seems to work, defining a variable in the abstract class, and then defining an alias of it in the child class, but I'm not so sure that it's a great practice.
The real problem I'm having is with the constructor. The constructor in Pair that accepts an array of Cards doesn't work, because to add the cards, I would first need to create the
collection (in this case, the ArrayList) before the parent constructor tries to add the cards.
Is there any way to work around this? Is this even a viable way of handling inheritance?
I think your biggest problem is that your just creating these classes without any real requirements. Is inheritance really the right choice here? It feels like you're designing the classes to fit a pre-conceived implementation instead of the other way around.
Define your interfaces for each class you need based on the real requirements, implement them, and then see if an abstract base class makes sense.
Just have each implementation pass down the collection type in the constructor:
public abstract class CardSet<C extends Collection<Card>>
{
protected final C elCollection;
public CardSet<Collection<Card>> cardSet()
{
return new CardSet<Collection<Card>>(new ArrayList<Card>());
}
public CardSet(C col){
this.elCollection = col;
}
public CardSet(C col, Card[] cards)
{
this(col);
for(Card c: cards)
elCollection.add(c);
}
public void add(Card c)
{
elCollection.add(c);
}
}
public class Pair extends CardSet<List<Card>>
{
public Pair()
{
super(new ArrayList<Card>());
}
public Pair(Card[] cards)
{
super(new ArrayList<Card>(), cards);
}
}
You may have to play a little bit with the declarations, but that should see you right
I'll give a quick (hackish?) answer: something you could do is have a protected abstract Collection<Card> createCollection() method defined in the base class, CardSet. Your subclasses would override this to create and return whatever type of collection is appropriate for that subclass. Then the superclass constructor would use that method to create the collection, after which it could go ahead and add the cards:
public CardSet(Card[] cards) {
super();
self.elCollection = createCollection();
Collections.addAll(self.elCollection, cards);
}
My feeling here is that you're trying to do 2 separate things with inheritance, so it seems confusing -
On the one hand, you have the concept of a set of cards. This will have a collection of cards. So first we know we have this:
public abstract class CardSet {
protected Collection<Card> cards;
}
At this point your classes should diverge, because what we have so far is the extent of the common behavior (well, we'll probably have some additional methods in there, like size(), nextCard(), isEmpty(), etc etc, that are easy enough to define on the protected collection, but never mind those now).
To use your own example
public class Deck extends CardSet {
public Deck (){
cards = new Stack<Card>();
}
public void shuffle() { ... }
}
public class Hand extends CardSet {
public Hand(){
//i'm using a set here instead of the list in your example because I
// don't think ordering is a property of a hand.
cards = new HashSet<Card>();
}
}
public class Pair extends CardSet {
...
}
Here cardSets are of different kinds. Thery're separated because they're expected to behave differently, and represent the additional behavior these types of collections have from the generalized notion of a card set. Trying to shoehorn additional code into the abstract parent may save a few lines but ultimately obfusactes.

Categories

Resources