I am trying to understand this clean code practice with an example. Consider a class Product having switch case for discount. I am trying to replace switch statement with polymorphism.
Before code:
class Product {
String priceCode;
int discount;
Product(String priceCode) {
setDiscount(priceCode);
}
public int getDiscount() {
return discount;
}
public void setDiscount(String priceCode) {
switch (priceCode) {
case "CODE1":
discount = // some logic;
case "CODE2":
discount = // some other logic;
case "CODE3":
discount = // some other logic;
}
}
}
In below code as you can see I removed switch statement but I still have if conditions to create an object of discountStrategy.
My question is I still have if conditions which I am trying to remove with Polymorphism.
After code:
class Product {
String priceCode;
DiscountStrategy discountStrategy;
Product(String priceCode) {
setDiscount(priceCode);
}
public int getDiscount() {
return discountStrategy.getDiscount();
}
public void setDiscount(String priceCode) {
if (priceCode.equals("CODE1")) {
discountStrategy = new DiscountStrategy1();
} else if (priceCode.equals("CODE2")) {
discountStrategy = new DiscountStrategy2();
}
// ...
}
}
interface DiscountStrategy {
public int getDiscount();
}
class DiscountStrategy1 implements DiscountStrategy {
public int getDiscount() {
// calculate & return discount;
}
}
class DiscountStrategy2 implements DiscountStrategy {
public int getDiscount() {
// calculate & return discount;
}
}
class DiscountStrategy3 implements DiscountStrategy {
public int getDiscount() {
// calculate & return discount;
}
}
Can you please help me understand this concept with better implementation of this example?
I think that Product class must not be aware about the discount creation process, it should only use a discount. So, my suggestion is to create a discount factory with a Map that will hold different discount implementations:
class DiscountFactory {
private static final Map<String, DiscountStrategy> strategies = new HashMap<>();
private static final DiscountStrategy DEFAULT_STRATEGY = () -> 0;
static {
strategies.put("code1", () -> 10);
strategies.put("code2", () -> 20);
}
public DiscountStrategy getDiscountStrategy(String priceCode) {
if (!strategies.containsKey(priceCode)) {
return DEFAULT_STRATEGY;
}
return strategies.get(priceCode);
}
}
After that, the Product class can be simplified:
class Product {
private DiscountStrategy discountStrategy;
Product(DiscountStrategy discountStrategy) {
this.discountStrategy = discountStrategy;
}
public int getDiscount() {
return discountStrategy.getDiscount();
}
}
Functional interface will allow you to create different implementations using lambda expressions:
interface DiscountStrategy {
int getDiscount();
}
And finally, example of the use of a product together with discount:
DiscountFactory factory = new DiscountFactory();
Product product = new Product(factory.getDiscountStrategy("code1"));
My two cents:
You will need to pass the parameters to discount() method.
a. Create a static class level HashMap of DiscountStrategy.
E.g :
map.put("CODE1", new DiscountStrategy1());
map.put("CODE2", new DiscountStrategy2());
b. wherever you need, you can simply use:
map.get(priceCode).discount()
Here is what you need to do
class Product {
String priceCode;
DiscountStrategy discountStrategy;
HashMap<String, DiscountStrategy> map=new HashMap();
Product(String priceCode) {
map.put("CODE1", new DiscountStrategy1());
map.put("CODE2", new DiscountStrategy2());
map.put("CODE3", new DiscountStrategy3());
setDiscount(priceCode);
}
public void setDiscount(String priceCode){
discountStrategy=map.get(priceCode);
}
public int getDiscount() {
return discountStrategy.getDiscount();
}
}
When, as is seems to be the case in Your example, the discount strategy is bound to a specific product type, I would compute the discount at the order item level.
For instance:
class Product {
double basePrice;
DiscountStrategy discountStrategy;
...
public double getBasePrice() {
return basePrice;
}
public DiscountStrategy getDiscountStrategy() {
return discountStrategy;
}
}
interface DiscountStrategy {
public double calculate(int quantity, Product product);
}
class OrderItem {
int quantity;
Product product;
public double getAmount() {
DiscountStrategy ds = product.getDiscountStrategy();
double discount = ds.calculate(quantity, product);
return quantity*(product.getBasePrice() - discount);
}
}
Example of discount strategy: quantity discount:
class QuantityRateDiscount implements DiscountStrategy {
static class QuantityRate {
final int minQuantity;
final double rate; // in %
QuantityRate(int minQuantity, double rate) {
this.minQuantity = minQuantity;
this.rate = rate;
}
}
QuantityRate[] rateTable;
// rateTable must be sorted by ascending minQuantity
QuantityRateDiscount(QuantityRate... rateTable) {
this.rateTable = rateRable.clone();
}
#Override
public double calculate(int quantity, Product product) {
QuantityRate qr = null;
for (QuantityRate qr2: rateTable) {
if (qr2.minQuantity > quantity) {
break;
}
qr = qr2;
}
if (qr != null) {
return product.getBasePrice()*qr.rate/100.0;
} else {
return 0;
}
}
}
Related
I am currently solving this issue. For example. I have two classes. One for item and second for some kind of records of these items. What I need to do, if I want to have a few methods for get average of different kind from all items in collection. In my example, there is average of price, mass and volume. The methods are repetitive and the only one different thing is the getter. So, is there any option, how to have one private method for counting and another three public, that will use this one private method and somehow put the field as argument?
Item class
public class Item {
private double price;
private double mass;
private double volume;
public double getPrice() {
return price;
}
public double getMass() {
return mass;
}
public double getVolume() {
return volume;
}
}
Stock class
package model;
import java.util.ArrayList;
import java.util.List;
public class Stock {
private List<Item> items;
public Stock() {
items = new ArrayList<>();
}
public double getAveragePrice() {
double sum = 0;
if (!items.isEmpty()) {
for (Item item : items) {
sum += item.getPrice();
}
return sum / items.size();
}
return sum;
}
public double getAverageMass() {
double sum = 0;
if (!items.isEmpty()) {
for (Item item : items) {
sum += item.getMass();
}
return sum / items.size();
}
return sum;
}
public double getAverageVolume() {
double sum = 0;
if (!items.isEmpty()) {
for (Item item : items) {
sum += item.getVolume();
}
return sum / items.size();
}
return sum;
}
}
As Johannes Kuhn proposed in a comment, you can pass the getter as a ToDoubleFunction<Item> to your calculation method:
private double calcAverage(ToDoubleFunction<Item> getter) {
double sum = 0;
if (!items.isEmpty()) {
for (Item item : items) {
sum += getter.applyAsDouble(item);
}
return sum / items.size();
}
return sum;
}
public double getAveragePrice() {
return calcAverage(Item::getPrice);
}
You can then reuse the calculation:
public double getAverageMass() {
return calcAverage(Item::getMass);
}
public double getAverageVolume() {
return calcAverage(Item::getVolume);
}
Here is a solution using stream API
public double getAverageMass() {
return calcAverage(Item::getMass);
}
public double getAverageVolume() {
return calcAverage(Item::getVolume);
}
private double calcAverage(ToDoubleFunction<Item> getter) {
return items.stream().mapToDouble(getter).average().orElse(0);
}
I have a simple pizza program in Java, made using the Factory Pattern.
Basically, when a factory is given as a parameter, it creates a particular pizza, which is then added to the list of pizzas of the PizzaShop.
I would like to create a method that displays how many particular pizzas I have. For instance, when the method is called, I would like it to display something like "We have 5 PizzaChicago and 3 PizzaNewYork". I am not sure how to do that.
This is my code.
public interface Pizza {
String name();
}
public class PizzaChicago implements Pizza{
public Integer price;
public PizzaChicago(Integer price){
this.price = price;
}
#Override
public String name() {
return this.getClass().getSimpleName();
}
}
public class PizzaNewYork implements Pizza{
public Integer price;
public PizzaNewYork(Integer price){
this.price = price;
}
#Override
public String name() {
return this.getClass().getSimpleName();
}
}
public interface PizzaFactory {
public Pizza createPizza(Integer price);
}
public class PizzaNewYorkFactory implements PizzaFactory{
#Override
public Pizza createPizza(Integer price) {
return new PizzaNewYork(6);
}
}
public class PizzaChicagoFactory implements PizzaFactory{
#Override
public Pizza createPizza(Integer price) {
return new PizzaChicago(8);
}
}
import java.util.ArrayList;
import java.util.List;
public class PizzaShop {
List<Pizza> pizzaList = new ArrayList<>();
public void createPizza(PizzaFactory factory, Integer price){
Pizza pizza = factory.createPizza(price);
System.out.println(pizza.name() + " " + "was created");
pizzaList.add(pizza);
}
}
`
What you have to do is iterate the list and check what is the type of every object.
int countPizzaNewYork = 0, countPizzaChicago = 0;
for(Pizza p: pizzaList){
if(p instanceOf PizzaNewYork)
{
countPizzaNewYork++;
}
else
{
countPizzaChicago++;
}
}
System.out.println("We have "+ countPizzaChicago+" PizzaChicago and "+countPizzaNewYork+" PizzaNewYork");
Alternate approach (use Map instead of a List):
public class PizzaShop {
Map<String, Integer> pizzaDiary = new HashMap<>();
public void createPizza(PizzaFactory factory, Integer price) {
Pizza pizza = factory.createPizza(price);
System.out.println(pizza.name() + " " + "was created");
int previousCount = pizzaDiary.getOrDefault(pizza.name(), 0);
pizzaDiary.put(pizza.name(), previousCount + 1);
}
}
Use the pizzaDiary map to print out your pizza counts.
Having a separate class for each pizza could be problematic and somewhat cumbersome. And for certain using the class name and instanceof is not the way to go. Have you considered using an Enum to represent the Pizzas? Here is an example of how it might work.
public class PizzaShopDemo {
enum Pizza {
CHICAGOPIZZA(8, "Chicago Pizza"),
NEWYORKPIZZA(6, "New York Pizza");
private double price;
private String name;
private int count = 0;
private Pizza(double price, String name) {
this.price = price;
this.name = name;
}
public double getPrice() {
return price;
}
public String getName() {
return name;
}
public void update() {
count++;
}
public int getCount() {
return count;
}
}
public static void main(String[] args) {
new PizzaShopDemo().pizzaShop();
}
public void pizzaShop() {
createPizza(Pizza.CHICAGOPIZZA);
createPizza(Pizza.NEWYORKPIZZA);
createPizza(Pizza.CHICAGOPIZZA);
createPizza(Pizza.NEWYORKPIZZA);
createPizza(Pizza.CHICAGOPIZZA);
createPizza(Pizza.CHICAGOPIZZA);
createPizza(Pizza.NEWYORKPIZZA);
for (Pizza p : Pizza.values()) {
System.out.println(p.getName() + ", " + p.getPrice()
+ ", " + p.getCount());
}
}
public void createPizza(Pizza type) {
type.update();
// other stuff here.
}
}
Prints
Chicago Pizza, 8.0, 4
New York Pizza, 6.0, 3
I am trying to write a program that will create a food order consisting of food items displayed from a menu. Each item is selected and given a certain quantity. I want to display the items that I select in a JTextField but it has not been working correctly.
There are a few problems that I have ran into and cannot seem to figure out,
The JOptionPane is supposed to display all of the items that I added to the deli arraylist, but it only displays the first one which is Nachos.
My getTotalPrice method is not properly calculating the cost and I'm not entirely sure why.
I want the program to determine if an item is already present in the Arraylist and add to the quantity if it does, and if not then add a new entry to the arraylist. However, it always adds a new item, regardless of if it exists already.
The following is my are all of my class files.
import java.util.ArrayList;
public class Menu {
private final ArrayList<Item> menu;
public Menu() {
menu = new ArrayList<>();
}
public void addItem(Item item) {
menu.add(item);
}
public Item getItem(int itemNo) {
if (menu.size() > itemNo) {
return menu.get(itemNo);
}
return null;
}
#Override
public String toString() {
for (int i = 0; i < menu.size(); i++) {
return String.format("%s: %s \n",i+1, menu.get(i));
}
return null;
}
}
public class Item {
private final String name;
private final double price;
public Item(String name, double price) {
this.name = name;
this.price = price;
}
public double getPrice() {
return price;
}
#Override
public String toString() {
return String.format("Name %s # Price $%s", name, price);
}
public boolean equals(Item item) {
return item.name.equals(item.name);
}
}
public class ItemQty {
private final Item item;
private final int quantity;
public ItemQty(Item item, int quantity) {
this.item = item;
this.quantity = quantity;
}
public Item getItem() {
return item;
}
public int getQuantity() {
return quantity;
}
#Override
public String toString() {
return String.format("%s - %s\n", quantity, item);
}
public boolean equals(ItemQty itemQty) {
return itemQty.getItem().equals(itemQty.getItem());
}
}
import java.util.ArrayList;
public class Order {
private final ArrayList<ItemQty> order;
public Order() {
order = new ArrayList<>();
}
public void addToOrder(ItemQty itemQty) {
if (order.contains(itemQty)) {
int amount = itemQty.getQuantity();
amount += 1;
}
else
order.add(itemQty);
}
public double getTotalPrice() {
for (int index = 0; index < order.size(); index++) {
double price = order.get(index).getItem().getPrice();
int quantity = order.get(index).getQuantity();
double sum = price * quantity;
return sum;
}
return 0;
}
#Override
public String toString() {
String str = "";
for (int index = 0; index < order.size(); index++) {
str += order.get(index).toString() + "\n\n";
}
return str;
}
}
Any help or critiques would be appreciated
My getTotalPrice method is not properly calculating the cost and I'm not entirely sure why.
This is due to the fact that you're returning the value of sum only after the first iteration of your loop
public double getTotalPrice() {
for (int index = 0; index < order.size(); index++) {
double price = order.get(index).getItem().getPrice();
int quantity = order.get(index).getQuantity();
double sum = price * quantity;
return sum;
}
return 0;
}
Something like...
public double getTotalPrice() {
double sum = 0;
for (Order item : order) {
double price = item.getItem().getPrice();
int quantity = item.getQuantity();
sum += (price * quantity);
}
return sum;
}
would work better
The JOptionPane is supposed to display all of the items that I added to the deli arraylist, but it only displays the first one which is Nachos.
Since there is no JOptionPane in your code, it's impossible to know what the issue might be
I want the program to determine if an item is already present in the Arraylist and add to the quantity if it does, and if not then add a new entry to the arraylist. However, it always adds a new item, regardless of if it exists already.
Okay, this is a lot more difficult, because you code doesn't really provide enough support to do it.
There's no way for your code to update the quantity information after the ItemQty is created, you will need to supply a setter of some kind to perform this action (or a add method, to which you pass another ItemQty and it does the job for you)
First, I'd add a new method to ItemQty
public class ItemQty {
//...
public void add(int quantity) {
this.quantity += quantity;
}
}
This just makes it possible to increase the quantity.
Second, I'd change the Order#addToOrder, I'd make it so you had to pass an Item and a quantity to it (other classes don't need to make a ItemQty object in this case). In this method, I'd search for a matching item and either update it or add it to the order.
public class Order {
//...
public void addToOrder(Item item, int quantity) {
List<ItemQty> matches = order.stream().filter((itemQty) -> {
return itemQty.getItem().equals(item);
}).collect(Collectors.toList());
if (matches.size() > 0) {
matches.get(0).add(quantity);
} else {
order.add(new ItemQty(item, quantity));
}
}
Okay, that might have you scratching your head, it does me, but basically, it's just a fancy pancy way for saying...
public void addToOrder(Item item, int quantity) {
ItemQty match = null;
for (ItemQty check : order) {
if (check.getItem().equals(item)) {
match = check;
break;
}
}
if (match != null) {
match.add(quantity);
} else {
order.add(new ItemQty(item, quantity));
}
}
This is my first post on stack exchange so i'm not really sure what you need but here's my issue:
I am creating an inventory tracker for my java class and I am running into an issue where I can't use the method addItem(Item newItem) because the class Inventory isn't static and does not have a constructor. We have a UML Diagram
we are supposed to work off of and it doesn't include a constructor for Inventory and says nothing about static.
I'm not really sure what else you need but any help would be greatly appreciated!
Thanks!
public class InventoryTrackerInterface {
public Inventory inv;
public static void main(String[] args) {
//test item
Item b1 = new Item("abc",1,123,"01345");
}
}
public class Inventory {
private Item[] itemArray;
private int totalItems = 0;
public int getTotalNumberOfItems() {
return totalItems;
}
public Item getItem(int index) {
if (index < 0 || index >= totalItems) {
return null;
} else {
return itemArray[index];
}
}
public void addItem(Item newItem) {
if (newItem == null) {
System.out.println("Item not added.");
} else {
itemArray[totalItems] = newItem;
totalItems++;
}
}
public void saveInventoryToFile(String fileName) {
}
public void loadInventoryFromFile(String fileName) {
}
}
public class Item {
private String name;
private int quantity;
private double price;
private String upc;
private Item() {
}
public Item(String name, int qty, double price, String upc) {
}
public String getName() {
return name;
}
public int getQuantity() {
return quantity;
}
public double getPrice() {
return price;
}
public String getUPC() {
return upc;
}
}
You do not need to explicitly define a constructor in order to instantiate a class. In such cases, a default constructor is automatically created.
UML diagrams will usually only indicate constructors in cases where you would need one with parameters, as in the case of Item.
You can either define your inv property as static:
public class InventoryTrackerInterface
{
public static Inventory inv;
public static void main(String args[])
{
// Test items
Item b2 = new Item("abc",1,123,"01345");
Item c2 = new Item("dfe",2,456,"56789");
// Inventory object
inv = new Inventory();
inv.addItem(b2);
inv.addItem(c2);
}
}
Or access it through an InventoryTrackerInterface instance:
public class InventoryTrackerInterface
{
public Inventory inv;
public static void main(String args[])
{
// Test items
Item b2 = new Item("abc",1,123,"01345");
Item c2 = new Item("dfe",2,456,"56789");
InventoryTrackerInterface instance = new InventoryTrackerInterface();
// Inventory object
instance.inv = new Inventory();
instance.inv.addItem(b2);
instance.inv.addItem(c2);
}
}
I have a class called x which is a array list and needs to be sorted in Decreasing order by Value.
My Class-
public static class x
{
public int id;
public double value;
public x(int _id, double _value)
{
id = _id;
value = _value;
//System.out.println(Integer.toString(id));
}
public Integer getID(){
return id;
}
public double getValue(){
return value;
}
//Sorting
public static Comparator<x> getComparator(SortParameter... sortParameters) {
return new xComparator(sortParameters);
}
public enum SortParameter {
VAL_DESCENDING
}
private static class xComparator implements Comparator<x> {
private SortParameter[] parameters;
private xComparator(SortParameter[] parameters) {
this.parameters = parameters;
}
public int compare(x o1, x o2) {
int comparison;
for (SortParameter parameter : parameters) {
switch (parameter) {
case VAL_DESCENDING:
comparison = o2.id - o1.id;
if (comparison != 0) return comparison;
break;
}
}
return 0;
}
}
}
I Call it like:
cp = x.getComparator(x.SortParameter.VAL_DESCENDING);
Collections.sort(attr1, cp);
attr1 is my array list
Just for Reference I am following this
I am getting error:
cannot find symbol : variable cp
I am a newbie to java :(
try using Comparator<x> cp = x.getComparator(x.SortParameter.VAL_DESCENDING); to declare it. you can not use a variable until it is declared