Better way/design than using large number of getters - java

Say I have a class which stores a weapon Arsenal
public class Arsenal{
List<RocketLauncher> rocketLaunchers;
List<HandGrenade> handGrenades;
List<LandMine> landMines;
//Followed by a few more weapons
}
The weapons are part of an enum, Weapon
Now, I am trying to display a summary screen or report elsewhere and I am preparing a map. See the snippet below. I have shown two approaches
public Map<Weapon,Integer> generateReport(Arsenal arsenal){
Map<Weapon,Integer> weaponCountMap = //suitable map impl
//Technique 1
//Create and use a larger num of getters from the Arsenal Class
weaponCountMap.put(Weapon.HAND_GRENADE, arsenal.getHandGrenades);
//.. and so on
//Technique 2
//Create a single method in Arsenal which returns a Map
weaponCountMap = arsenal.getSummary();
return weaponCountMap;
}
Question : Is it just me or does everyone feel 'not right' to use a large number of getters. Suppose Arsenal stores around 50 weapons, it's like 50 methods in the class. Double with setters.
Also. I feel less flexible using the 2nd method, with no accessor methods.
Can you guys please critically evaluate both approaches and possibly suggest new ones?

How about not hard-coding types of weapon inside of your Arsenal? The following is simple implementation of heterogeneous container for your specific case. However, as I don't quite familiar with Generics in enum, this implementation is when you have Weapon and their subclasses, e.g. HardGrenade extends Weapon, etc.
public class Arsenal{
private Map<Class<?>, Collection<?>> weaponArsenal;
public <T extends Weapon> Collection<T> get(Class<T> weaponClass) {
if (weaponArsenal.containsKey(weaponClass) {
return (Collection<T>) weaponArsenal.get(weaponClass);
}
return new ArrayList<T>(); // to avoid checking null everytime in client code
}
public <T extends Weapon> void put(T weapon) {
if (!weaponArsenal.containsKey(weapon.class)) {
Collection<T> weaponList = // initialize correct collection here
weaponArsenal.put(weapon.class, weaponList);
}
weaponArsenal.get(weapon.class).add(weapon);
}
}
and in the client code
Arsenal arsenal = new Arsenal();
arsenal.put(new HandGrenade());
arsenal.put(new RocketLauncher());
Collection<HandGrenade> handGrenades = arsenal.get(HandGrenade.class);
Collection<RocketLauncher> rocketLaunchers = arsenal.get(RocketLauncher.class);

In the arsenal you can duplicate the map instead of using lists. Then in the generateReport method you can iterate over the enum and use the enum value to get the suitable list from the map.
Something like
Arsenal:
Map<Weapon,List<Weapon>> weaponsMap;
arsenalMap.put(Weapon.HAND_GRENADE,handGrenades);
generate report:
for (Weapon weapon: Weapon.values()) {
weaponCountMap.put(Weapon.HAND_GRENADE, arsenal.weaponsMap.get(weapon));
}
Might not be the best solution but you will remove some of the getters.

If you make Arsenal immutable and construct it using a builder (to avoid having a bunch of constructors), you can make the instance variables public.
This approach allows you to use technique 1 from your question but without getters, and still keep state management of the object internal to the object.
public class Arsenal {
public final List<RocketLauncher> rocketLaunchers;
public final List<HandGrenade> handGrenades;
public final List<LandMine> landMines;
//Followed by a few more weapons
private Arsenal(final Arsenal.Builder builder) {
this.rocketLaunchers = Collections.unmodifiableList(builder.rocketLaunchers);
this.handGrenades = Collections.unmodifiableList(builder.handGrenades );
this.landMines= Collections.unmodifiableList(builder.landMines);
// and so on
}
public static class Builder {
private final List<RocketLauncher> rocketLaunchers = new ArrayList<>();
private final List<HandGrenade> handGrenades = new ArrayList<>();
private final List<LandMine> landMines = new ArrayList<>();
public Builder rocketLaunchers(List<RocketLauncher> rocketLaunchers) {
this.rocketLaunchers.addAll(rocketLaunchers);
return this;
}
public Builder handGrenades(List<HandGrenade> handGrenades) {
this.handGrenades.addAll(handGrenades);
return this;
}
public Builder landMines (List<LandMines> landMines ) {
this.landMines .addAll(landMines );
return this;
}
public Arsenal build() {
return new Arsenal(this);
}
}
}
You can now use this in the following way.
List<RocketLauncher> rocketLaunchers = //whatever
Arsenal arsenal = new Arsenal.Builder().rocketLaunchers(rocketLaunchers).build();
....
weaponCountMap.put(Weapon.HAND_GRENADE, arsenal.handGrenades);
//.. and so on
All fields of the arsenal are non-null, and can't be modified. If you define the builder in an external class (i.e. not a static class within Arsenal) your Arsenal class will be very small - just fields and the constructor, plus logic methods.

Please have a look at other posts like this one...Java Conventions: use getters/setters WITHIN the class? to obtain an idea on when you could use getters instead of direct access.
Its kind of a design question depending upon what you would like to encapsulate within your arsenal class and what you would like to expose to the outside world for viewing your arsenal.
Its like the UN trying to inspect your arsenal and you tell them hey I wont tell you what weapons I am dealing with in my arsenal but I can give you a report and that is the report you externalize to the outside world. Now it depends on you what report you want to give out and which weapons will land in that map of yours.
Now looking at your second technique, are you planning to shift the map creation logic into your Arsenal class.
Another design question to answer .... is this logic just to obtain reports then my suggestion is to keep it out of the arsenal class and keep it light.
Otherwise you might end up putting in all kind of report logics into the arsenal class and it will get heavier and might explode.

See https://projectlombok.org/ or https://github.com/google/auto/tree/master/value.
Add separate field for each weapon, Lombok or AutoValue will create getters/setters/hashcode/equals/toString methods for you.

Related

Is it possible to have an enum where each item is associated with a class?

I'm creating a game with hundreds of abilities, so trying to leverage abstracts and generics as much as possible.
Each ability extends an abstract Ability class with universal methods like getCooldown(player), which gets a specific ability's cooldown for a player. Inheritance saves me from having to duplicate that code in every ability class.
public abstract class Ability {
public static String getCooldown() {
int cooldown;
//logic to get cooldown in milliseconds
return cooldown;
}
}
But the logic and metadata for each ability are unique and coded like such:
public class Parry extends Ability {
public static String getDescription() {
...
}
public static void castAbility() {
...
}
}
Here's my enum. I'm using an enum because abilities and their metadata are constants that are ideally available at compile time. I also don't want to store the metadata separate from the classes which have the rest of the ability logic.
public enum AbilityEnum {
BORN_READY(BornReady.class),
JUGGLER(Juggler.class),
...
PARRY(Parry.class);
public final Class<? extends Ability> cls;
AbilityEnum(Class<? extends Ability> cls) {
this.cls = cls;
}
}
In other parts of the codebase, I want to use the Enum to generically get basic info on an ability, cast a spell, etc. I want to avoid hard-coding for any specific ability because there are 200+ of them. For example, when a player opens their skill menu, I need to grab the descriptions for every ability. I'd rather not type [ability_name].getDescription() 200+ times.
for (AbilityEnum ability : AbilityEnum.values()) {
String tooltip = ability.cls.getDescription();
...
// load descriptions into menu system so players
// can hover abilities for a tooltip description
}
If I try to run this I get the error:
Cannot resolve method 'getDescription' in 'Class'
This confuses me because I bounded the generic, so why does it think it has a Class instead of an Ability? I think I'm either misusing generics or have the wrong syntax for calling methods this way. Perhaps I should be using a list or something else instead of an enum?

What's the best way to DRY Java code ? Creating private method with different Objects for parameters?

I'm creating a RTS game and one of the features is to construct differend kind of buildings. I'm finding a lot of repetition and I was thinking to extract it in helper method, but the problem is that every building is different object which inharits some propertyes from the main building class.
The building methods looks like this:
public static void buildDockyard(Base base) {
if (Validator.checkForBuilding(base, "Dockyard")) {
throw new IllegalStateException("Dockyard is already build");
}
Dockyard dockyard = new Dockyard("Dockyard");
int requiredPower = dockyard.requiredResource("power");
int requiredStardust = dockyard.requiredResource("stardust");
int requiredPopulation = dockyard.requiredResource("population");
Validator.checkResource(base, requiredPower, requiredStardust, requiredPopulation);
updateResourceAfterBuild(base, requiredPower, requiredStardust, requiredPopulation);
dockyard.setCompleteTime(dockyard.requiredResource("time"));
base.getBuildings().add(dockyard);
}
public static void buildHotel(Base base) {
if (Validator.checkForBuilding(base, "Space Hotel")) {
throw new IllegalStateException("Space Hotel is already build");
}
SpaceHotel spaceHotel = new SpaceHotel("Space Hotel");
int requiredPower = spaceHotel.requiredResource("power");
int requiredStardust = spaceHotel.requiredResource("stardust");
int requiredPopulation = spaceHotel.requiredResource("population");
Validator.checkResource(base, requiredPower, requiredStardust, requiredPopulation);
updateResourceAfterBuild(base, requiredPower, requiredStardust, requiredPopulation);
spaceHotel.setCompleteTime(spaceHotel.requiredResource("time"));
base.getBuildings().add(spaceHotel);
base.setCapacity(base.getCapacity() + spaceHotel.getCapacity());
}
I was thinking to refactor like this:
The helper method
private static void construct(Building building, Base base) {
int requiredPower = building.requiredResource("power");
int requiredStardust = building.requiredResource("stardust");
int requiredPopulation = building.requiredResource("population");
Validator.checkResource(base, requiredPower, requiredStardust, requiredPopulation);
updateResourceAfterBuild(base, requiredPower, requiredStardust, requiredPopulation);
building.setCompleteTime(building.requiredResource("time"));
}
Aimed result
public static void buildDockyard(Base base) {
if (Validator.checkForBuilding(base, "Dockyard")) {
throw new IllegalStateException("Dockyard is already build");
}
Dockyard dockyard = new Dockyard("Dockyard");
construct(dockyar, base);
base.getBuildings().add(dockyard);
}
The problem is that each building has unique properties and resource requirements and the main Building class doesn't know about them, so I can't use it as a parameter in the helper method.
All of this is happening in a static helper class for the Base class.
How would you refactor this code ?
Thank you in advance !
Your problems start with using static methods for everything. In an object oriented world you ideally have an object Base and it would have a non-static method addStructure(Struture structure) were Structure is an interface for example. Now you would have objects like Building and Dockyard which would implement Structure.
Implentation of addStructure would be something like this:
if (getBuildings().contains(structure)) {
throw new IllegalStateException(structure.name + " is already build");
}
if (validateStillHaveEnoughResourcesFor(structure)) {
throw new IllegalStateException(structure.name + " can not be added. Not enough resources");
}
getBuildings().add(structure);
Validating structure itself should not be in base. Validating how structure fits to the base should be in the base.
The best way to DRY in Java when making games is to have a clear understanding and terminology of your game. If you read any modern board game manual you will soon see that they will use exactly one word for one concept, like Turn, Round, Building, Player, Resource. This allows to form a rough structure: A Building costs a certain amount of Resource. If a player hasn't enough of Resource then tell him "We need more vespine gas.", etc. The clearer the picture, the DRY-er your Java and easier to create the necessary Classes for your code.
Parameters
If you end up with something like this:
public static void someFunction(Base base, Object param1, Object param2)
public static void someOtherFunc(Base base, Object paramA, Object paramB)
...
Then this is a strong hint that maybe both functions should be part of the Base class.
Enums
If you have a limited set of values then Java Enums can be fantastic to represent them, e.g. your Resource system:
public enum Resource {
POWER, STARDUST, POPULATION
}
Now you don't have to remember if you called it "stardust", "Stardust" or if you even still have a Resource like "stardust". Instead you can use int requiredPower = building.requiredResource(Resource.POWER);
Polymorphism
Let's suppose we have two classes, Building and StarHotel, with StarHotel being a specific kind of Building. Having an abstract class Building allows us to handle some general mechanics in a specific manner, like this:
public abstract class Building {
private ... cost;
private ... requirements;
private ...
// Std-Getter and Setter methods
public ... getCost() { return this.cost; }
}
EVERY Building has a cost, and requirements and other important variables. BUT we handled all the standard stuff of getting and setting these generic variables to a base class from which we now can extend other, more specific buildings. Thanks to the extends keyword you can get the Cost of a StarHotel Object without filling the StarHotel class with repetitive Getters and Setters.
public class StarHotel extends Building {
// Getter, Setter inherited from Building class
}
Interfaces
Java Interfaces allow you to define Interfaces which define methods. In laymen terms: This is useful, because every Class that implements an Interface must implement the method, unless the interface provides the default implementation.
public interface ResourceProvider {
void provideResourceFor(Base base); // A Resource Provider provides Resource for a base.
}
With this interface we have defined that if some Class implements ResourceProvider it has to specify how and what resources to provide for some Base object. Our interface does not care which Resource, which Base and even what provideResourceFor could mean, but as long as something implements ResourceProvider it has to provide the functionality.
Putting all together
Putting Enums, Interface and Polymorphism together, we can now create a StarHotel class that extends Building and implements ResourceProvider, providing 8 Food units and 2 Happiness units to our Base.
public class StarHotel extends Building implements ResourceProvider
public void provideResourceFor(Base base) {
base.addResource(Resource.FOOD, 8);
base.addResource(Resource.HAPPINESS, 2);
}
}
That might be much to take in, but hopefully it will give you a good direction where to look further.

Merging duplicate code that use different objects

I use two api calls to get data about vehicleUtils depending on contentFilter.
I have very similar code for both (drivers and vehicles). What i tried to do is to extract the code into a single method and apply Strategy pattern like they suggest here Refactoring methods, but i could not figure out how to implement it. Am i using a good approach or is there any better way?
if (contentFilter.equalsIgnoreCase(Contentfilters.VEHICLES.toString())) {
VuScores vuScores = new VuScores();
List<VehicleVuScores> vehicleVuScoresList = new ArrayList<>();
List<VehicleUtilization> vehicleUtilizations = RestClient.getVehicles(request).join().getVehicleUtilizations();
if (Objects.nonNull(vehicleUtilizations)) {
vehicleUtilizations.forEach(vehicleUtilization -> {
vuScores.getVehicleVuScores().forEach(vehicleVuScore -> {
vehicleVuScore.getScores().setTotal(vehicleUtilization.getFuelEfficiencyIndicators().getTotal().getValue());
vehicleVuScore.getScores().setBraking(vehicleUtilization.getFuelEfficiencyIndicators().getGroupIndicators().get(3).getIndicators().get(0).getValue());
vehicleVuScore.getScores().setCoasting(vehicleUtilization.getFuelEfficiencyIndicators().getGroupIndicators().get(3).getIndicators().get(1).getValue());
vehicleVuScore.getScores().setIdling(vehicleUtilization.getFuelEfficiencyIndicators().getGroupIndicators().get(0).getIndicators().get(0).getValue());
vehicleVuScore.getScores().setAnticipation(vehicleUtilization.getFuelEfficiencyIndicators().getGroupIndicators().get(3).getValue());
vehicleVuScore.getScores().setEngineAndGearUtilization(vehicleUtilization.getFuelEfficiencyIndicators().getGroupIndicators().get(1).getValue());
vehicleVuScore.getScores().setStandstill(vehicleUtilization.getFuelEfficiencyIndicators().getGroupIndicators().get(0).getValue());
vehicleVuScore.getScores().setWithinEconomy(vehicleUtilization.getFuelEfficiencyIndicators().getGroupIndicators().get(1).getIndicators().get(7).getValue());
vehicleVuScore.setAvgFuelConsumptionPer100Km(vehicleUtilization.getMeasures().getTotal().getAverageConsumption().getValue());
vehicleVuScore.setAvgSpeedDrivingKmh(vehicleUtilization.getMeasures().getTotal().getAverageSpeed().getValue());
vehicleVuScore.setEngineLoad(vehicleUtilization.getFuelEfficiencyIndicators().getGroupIndicators().get(1).getIndicators().get(1).getValue());
vehicleVuScore.setTotalDistanceInKm(vehicleUtilization.getMeasures().getDriving().getDistance().getValue());
vehicleVuScore.setTotalTime(Math.toIntExact(vehicleUtilization.getMeasures().getTotal().getTime().getValue()));
vehicleVuScoresList.add(vehicleVuScore);
});
});
vuScores.setVehicleVuScores(vehicleVuScoresList);
}
return CompletableFuture.completedFuture(vuScores);
} else if (contentFilter.equalsIgnoreCase(Contentfilters.DRIVERS.toString())) {
VuScores vuScores = new VuScores();
List<DriverVuScores> driverVuScoresList = new ArrayList<>();
List<VehicleUtilization> vehicleUtilizations = RestClient.getDrivers(request).join().getVehicleUtilizations();
if (Objects.nonNull(vehicleUtilizations)) {
vehicleUtilizations.forEach(vehicleUtilization -> {
vuScores.getDriverVuScores().forEach(driverVuScores -> {
driverVuScores.getScores().setTotal(vehicleUtilization.getFuelEfficiencyIndicators().getTotal().getValue());
driverVuScores.getScores().setBraking(vehicleUtilization.getFuelEfficiencyIndicators().getGroupIndicators().get(3).getIndicators().get(0).getValue());
driverVuScores.getScores().setCoasting(vehicleUtilization.getFuelEfficiencyIndicators().getGroupIndicators().get(3).getIndicators().get(1).getValue());
driverVuScores.getScores().setIdling(vehicleUtilization.getFuelEfficiencyIndicators().getGroupIndicators().get(0).getIndicators().get(0).getValue());
driverVuScores.getScores().setAnticipation(vehicleUtilization.getFuelEfficiencyIndicators().getGroupIndicators().get(3).getValue());
driverVuScores.getScores().setEngineAndGearUtilization(vehicleUtilization.getFuelEfficiencyIndicators().getGroupIndicators().get(1).getValue());
driverVuScores.getScores().setStandstill(vehicleUtilization.getFuelEfficiencyIndicators().getGroupIndicators().get(0).getValue());
driverVuScores.getScores().setWithinEconomy(vehicleUtilization.getFuelEfficiencyIndicators().getGroupIndicators().get(1).getIndicators().get(7).getValue());
driverVuScores.setAvgFuelConsumptionPer100Km(vehicleUtilization.getMeasures().getTotal().getAverageConsumption().getValue());
driverVuScores.setAvgSpeedDrivingKmh(vehicleUtilization.getMeasures().getTotal().getAverageSpeed().getValue());
driverVuScores.setEngineLoad(vehicleUtilization.getFuelEfficiencyIndicators().getGroupIndicators().get(1).getIndicators().get(1).getValue());
driverVuScores.setTotalDistanceInKm(vehicleUtilization.getMeasures().getDriving().getDistance().getValue());
driverVuScores.setTotalTime(Math.toIntExact(vehicleUtilization.getMeasures().getTotal().getTime().getValue()));
driverVuScoresList.add(driverVuScores);
});
});
vuScores.setDriverVuScores(driverVuScoresList);
}
return CompletableFuture.completedFuture(vuScores);
}
Try to think about a common (abstract) base class, that holds the common code. The actual classes hold the differing code.
You then don't need to to work with instanceof or Contentfilters or whatever kind decission functionality you use. You just can call the common methods, as your function should take the (abstract) base class. This really removes code duplication.
Use an interface, implement it in both the classes, and use that interface in both places to get or set values.
Since all the method names are same, the interface should contain all the necessary getters and setters.
This way you won't have to use different classes.
So, everything is the same except
the types of the DTO you copy the data to (VehicleVuScores vs DriverVuScores)
the RestClient method invoked
The main challenge is sharing the code that invokes the setters. We need a way to refer to the target object without knowing whether its a VehicleVuScores or a DriverVuScores. We could declare it as:
Object vuScores;
but since Object doesn't declare the setters, we'd get compilation errors when trying to invoke the setters. To fix that, we can move the declaration of these getters and setters into a common base type:
abstract class VuScoresBase {
// fields, getters and setters
}
class DriverVuScores extends VuScoresBase {}
class VehicleVuScores extends VuScoresBase {}
so we can write:
public void convert(VehicleUtilization vehicleUtilization, VuScoresBase result) {
// invoke the setters here
}
and use this method in both cases.
With generics, we could also reuse the iteration code:
<V extends VuScoresBase> public void convertList(List<VehicleUtilization> vehicleUtilizations, List<V> resultList, Supplier<V> constructor) {
// iterate
V vuScore = constructor.apply();
convert(vehicleUtilization, vuScore);
resultList.add(vuScore);
}
so we could invoke it with
convertList(vehicleUtilizations, driverVuScores, DriverVuScore::new);
but i'd probably refrain from that, because the generics make the code hard to understand.
However, since the DriverVuScores and VehicleVuScores are so similar, I'd question whether we really need them to be separate types. If we can use VuScoresBase everywhere, this would vastly simplify the conversion logic:
VuScoresBase convert(VehicleUtilization vehicleUtilization) {
VuScoresBase vuScores = new VuScoreBase();
// invoke setters
return vuScores;
}
and
List<VuScoresBase> convertList(List<VehicleUtilization> vehicleUtilizations) {
// iterate
result.add(convert(vehicleUtilization));
}

Abstract Class Hold Member Field or Use Getter() of Concrete Implementation?

One OOP principle I'm struggling with is Abstract Classes and their ability to hold member fields (variables).
Take for instance the following code (example 1):
public abstract class AbstractClassConstructor {
List mList;
public AbstractClassConstructor(List list) {
mList = list;
}
public Object getListRow(int row) {
return mList.get(row);
}
}
And please inspect this alternative code which provides the same functionality (example 2):
public abstract class AbstractClassGetter {
public abstract List getList();
public Object getListRow(int row) {
return getList().get(row);
}
}
I'm concerned with whether example 1 or example 2 is more maintainable, as well, I would like to follow OOP standards as close as possible.
Currently I see more coupling in example 1 than I do example 2, though I don't see many other issues. Example 2 however is more straight-forward, I like that the concrete implementation holds the private field and the AbstractClassGetter uses a method to fetch it. This feels better, but I'm struggling to apply the correct OOP principle to help me know which IS better from this perspective.
As for me, example 1 would not be ideal if mList will be used in the child class with mList's function that is specific to its type (e.g. LinkedList or ArrayList). This is because it might need to be cast to its actual type before it could get used. In this case example 2 would be better.
If there's no function of specific type needed in the child class, then example 1 would be better in term of encapsulation.
Updated
Another approach, where perhaps could be considered the middle ground is to use Generic.
public abstract class AbstractClassConstructor<T extends List> {
T mList;
public AbstractClassConstructor(T list) {
mList = list;
}
public Object getListRow(int row) {
return mList.get(row);
}
}

How to avoid creating the same object reference multiple times?

Imagine that I have some classes that looks like this:
class Car {
private Image carImage;
public Car(int imageIndex) {
switch (imageIndex) {
case 1: carImage = generateCarImage(1); break;
# and so forth
}
}
}
class Audi extends Car {
private int numberOfSeats;
public Audi(int imageIndex, int numberOfSeats) {
super(imageIndex);
this.numberOfSeats = numberOfSeats;
}
}
Now imagine that I create multiple Audi's using the same image:
Audi car1 = new Audi(1,2);
Audi car2 = new Audi(1,3);
Will car1 and car2 extend the same object? I assume not, but is there a way I can make it so? I'm asking because I want to avoid generating and storing the same image twice.
EDIT:
Will these two audi's reference the same car, e.g. the image is generated and stored only once, and any changes to one affects the other?
class Car {
private Image carImage;
public Car(int imageIndex) {
switch (imageIndex) {
case 1: # carImage = readfile(1.jpeg)
# and so forth
}
}
}
class Audi{
private int numberOfSeats;
private Car car;
public Audi(Car car, int numberOfSeats) {
this.car = car;
this.numberOfSeats = numberOfSeats;
}
}
Car car = new Car(1);
Audi audi1 = new Audi(car,2);
Audi audi2 = new Audi(car,2);
EDIT 2:
There are a lot of good answers here, and I ended up using a combination of them to create a decent solution. My initial problem was not very well defined, mainly because I didn't know myself exactly what it was.
Anyway, for this problem it is not possible to generate all the data (PartsInfo in the example below) beforehand, nor can I generate the data explicitly (as implied by the switch-case example above). The biggest problem with the solution below is that I can't access individual fields in PartsInfo without retrieving the whole thing (as is done in the solution when Car.getPartsInfo() is called) or creating multiple instances of the same object (in which case the Car class would get its own PartsInfo variable).
A weak hashmap would also do, but not optimal because the problem is not garbage collection, but huge amount of identical data stored in separate instances.
The solution is applicable if the ID is something like "audi-a4-2003" and PartsInfo is identical for all "audi-a4-2003" independent of color, owner, age, number of seats etc, but completely different for "audi-a4-2004".
Thanks
Class PartsInfo {
// lots of stuff I'd rather not create nor save multiple times
}
Class PartsInfoFactory {
private static HashMap<String, PartsInfo> partsInfoMap = new HashMap<String, PartsInfo>();
public static getPartsInfo(String id) {
if (!partsInfoMap.containsKey(id)) {
generatePartsInfo(id);
}
return partsInfoMap(id)
}
private static generatePartsInfo(String id) {
// Do stuff I don't want to do twice for same ID
partsInfoMap.put(id)
}
}
Class Car {
private Color color;
private String id;
// Notice that PartsInfo is not stored here
public Car(Color color, String id) {
this.color = color;
this.id = id;
}
public PartsInfo getPartsInfo() {
return PartsInfoFactory.getPartsInfo(id);
}
}
Will car1 and car2 extend the same object?
A class can extend from another class.. Objects do not extend anything. In Java, inheritance is just for classes and interfaces. What you're doing here is creating two instances of the same class, Audi, and Audi extends from Car.
is there a way I can make it so?
No.
I'm asking because I want to avoid generating and storing the same image twice.
This is the proper question to answer. Your real problem is dealing with avoiding to create the same object instance multiple times. For this, it will be better to use an object pool by making use of a WeakHashMap. Here's an explanation on why to use this structure: When would you use a WeakHashMap or a WeakReference?
A good way to avoid creating the same image multiple times is to use dependency injection: inject the image as a constructor parameter, rather than passing in the parameter to generateCarImage:
class Car {
private final Image image;
Car(Image image) {
this.image = image;
}
}
class Audi extends Car {
Audi(Image image, int numDoors) {
super(image);
// ...
}
}
This means that image can come from anywhere - giving you more explicit control over the lifecycle of the images. So, if you want to use the same image over and over, you can, and it's obvious that you are:
Image image = generateCarImage(1);
Audi car1 = new Audi(image, 4);
Audi car2 = new Audi(image, 2);
Also, by removing static coupling to your generateCarImage method, it makes the class more testable, since you can create different images for testing, e.g. that are simpler to generate.
You never extend objects, you extend the class. And of course you will be extending the same class all the time.
Everytime you're using the new clause you will be creating a new instance of the object, a complete separate representation of the class; so answering to the direct question: no, you're not extending the object.
The underlying question is that you may not want to repeat the creation of to equal images: Then you must make a different approach. I recomend first to do another read to the OO aspect of Java, then think on (maybe) the factory patter which could be a class that will take care of not repeating the creation of to equal images if another was already created.
In Java there is no such thing as extending an object (other languages have this kind of inheritance, called prototypal. However, Java does not have prototypal inheritance; only a class inheritance).
Extending in Java means extending a class, not an object, which is an instance of a class.
Therefore, although the classes of car1 and car2 extend the same class, the two objects are unrelated to each other.
I want to avoid generating and storing the same image twice
There is no problem with multiple objects sharing a third object, which in your case could be an image. One way to deal with this would be creating an image cache common to all instances of Car, generate the image the first time that it is requested, and then re-using the same image object as needed to save space:
Is it possible to, instead of searching a cache of images, searching through a cache of all instances of Car, and then choose which one to instantiate in the Audi class?
You cannot instantiate an object for a second time. However, you can make a cache of Car objects, and implement a factory method on the Car that searches its cache for a suitable car before making a new instance.
My solution is using static references to be used as constant values. This is the easiest solution, given that enum won't work with objects, since it has to be evaluated at compile-time.
But we want to get both a run-time constants, and the benefit of using an enum like using single-instance and can be used in a switch statement.
So we are going to implement the enum to return constant static attributes of another class which is available at compile-time, and return a constant reference to an object created on run-time.
class CarImageDirectory
{
// Created at Run-time
public static final Image Audi = new Image("Audi");
public static final Image Toyota = new Image("Toyota");
// ..etc
}
enum CarImage
{
// Created at Compile-time
Audi
{
#Override public Image image () { return CarImageDirectory.Audi; }
},
Toyota
{
#Override public Image image () { return CarImageDirectory.Toyota; }
}; // ..etc
public abstract Image image ();
}
CarImage will work like this:
CarImage A = CarImage.Audi;
CarImage B = CarImage.Audi;
if (A == B) System.out.println("A and B are both Audi");
Then we just define our Car class using it:
class Car
{
private CarImage carImg;
public Car (CarImage carImg) { this.carImg = carImg; }
public Image getImage () { return carImg.image(); }
public CarImage getCarImage () { return carImg; }
}
class AudiCar extends Car
{
private int numOfSeats;
public AudiCar (int numOfSeats)
{
super(CarImage.Audi);
this.numOfSeats = numOfSeats;
}
}
class ToyotaCar extends Car
{
private int numOfSeats;
public ToyotaCar (int numOfSeats)
{
super(CarImage.Toyota);
this.numOfSeats = numOfSeats;
}
}
Also CarImage itself can be used in switch statement too:
CarImage A = CarImage.Audi;
switch(A)
{
case CarImage.Audi:
System.out.println("This car is Audi");
break;
case CarImage.Toyota:
System.out.println("This car is Toyota");
break;
default:
}
Have you looked into "flyweight" pattern? That might reduce object creation for you.
Technically, it's for reducing memory footprint, but if object creation is expensive and there is high reuse, you can use it in situations where startup time is not an issue, such as with application-server startups.
In any event only optimize if you know it's a performance problem.
Hope this helps!

Categories