Taxi Meter App Business Logic Using TDDObject Paradigm for PHP, Practice in DesignTDD for Euler Problem...
Strange Sign on Lab Door
Why did other German political parties disband so fast when Hitler was appointed chancellor?
Can we use the stored gravitational potential energy of a building to produce power?
How to avoid being sexist when trying to employ someone to function in a very sexist environment?
If I sold a PS4 game I owned the disc for, can I reinstall it digitally?
Jumping Numbers
We are very unlucky in my court
What is better: yes / no radio, or simple checkbox?
Word or phrase for showing great skill at something without formal training in it
Slow moving projectiles from a hand-held weapon - how do they reach the target?
Lick explanation
What is the purpose of easy combat scenarios that don't need resource expenditure?
Dilemma of explaining to interviewer that he is the reason for declining second interview
Where are a monster’s hit dice found in the stat block?
Solving Fredholm Equation of the second kind
Pre-1980's science fiction short story: alien disguised as a woman shot by a gangster, has tentacles coming out of her breasts when remaking her body
Why does a metal block make a shrill sound but not a wooden block upon hammering?
Help Me simplify: C*(A+B) + ~A*B
Checking for the existence of multiple directories
How to deal with an incendiary email that was recalled
Does Improved Divine Strike trigger when a paladin makes an unarmed strike?
Process to change collation on a database
Is there any differences between "Gucken" and "Schauen"?
Would a National Army of mercenaries be a feasible idea?
Taxi Meter App Business Logic Using TDD
Object Paradigm for PHP, Practice in DesignTDD for Euler Problem #4Business logic in service methodImplementing common fraction using TDDBusiness logic parallelization engineEvent log implementation with TDDAssembly Line Scheduling challenge, solved using TDDA TDD Exercise on Snakes and LaddersBusiness logic inside MVC Cart Controller methodMars Rover Kata using TDD and SOLID
$begingroup$
The following problem has been written using TDD. Please review the following:
- Unit tests
- OOP
- Design Principles (SOLID)
- Cleanliness and naming
- Anything that could make the program better.
Problem Statement:
Taxi Meter Company has to develop an app for their business. The app calculates the bill based on the car type and the distance covered. They have 3 different types of car and they charge the base price for the first 5 miles as
follows:
- Mini - $100
- Light - $200
- Compact - $300
After they cross 5 miles, the customer will be charged at 10$/mile
for the next 10 miles. Once they cross 15 miles, they will be charged at 20$/mile.
If the total distance covered is greater than 100 miles, they will be charged at 10$/mile for every mile (the total distance). Base price will be added by default in addition to the charges for the miles. Please refer sample input and output.
Sample Input:
- Mini, 10 Miles
- Light, 5 Miles
- Mini, 20 Miles
- Compact, 120 Miles
Sample Output:
- 150
- 200
- 300
- 1500
Test cases (Unit tests:)
public class TaxiMeterAppSpec {
@Test
public void givenTheDistanceCoveredForTheFirst5KmsAndTheCarTypeAsMiniTheBaseFareShouldBe100() {
Meter meter = new Meter(5);
CarType car = new Mini(meter);
TaxiMeterApp taxiMeter = new TaxiMeterApp(car);
assertEquals(100, taxiMeter.showPrice());
}
@Test
public void givenTheDistanceCoveredForTheFirst5KmsAndTheCarTypeAsLightTheBaseFareShouldBe200() {
Meter meter = new Meter(5);
CarType car = new Light(meter);
TaxiMeterApp taxiMeter = new TaxiMeterApp(car);
assertEquals(200, taxiMeter.showPrice());
}
@Test
public void givenTheDistanceCoveredForTheFirst5KmsAndTheCarTypeAsCompactTheBaseFareShouldBe300() {
Meter meter = new Meter(5);
CarType car = new Compact(meter);
TaxiMeterApp taxiMeter = new TaxiMeterApp(car);
assertEquals(300, taxiMeter.showPrice());
}
@Test
public void ifTheDistanceIsMoreThan100KmsAndTheCarTypeIsCompactTheTotalFareShouldBe1800() {
Meter meter = new Meter(150);
CarType car = new Compact(meter);
TaxiMeterApp taxiMeter = new TaxiMeterApp(car);
assertEquals(1800, taxiMeter.showPrice());
}
@Test
public void ifTheDistanceIsMoreThan100KmsAndTheCarTypeIsMiniTheTotalFareShouldBe1600() {
Meter meter = new Meter(150);
CarType car = new Mini(meter);
TaxiMeterApp taxiMeter = new TaxiMeterApp(car);
assertEquals(1600, taxiMeter.showPrice());
}
@Test
public void ifTheDistanceIsBetween16And100KmsAndTheCarTypeIsMiniTheTotalFareShouldBe900() {
Meter meter = new Meter(50);
CarType car = new Mini(meter);
TaxiMeterApp taxiMeter = new TaxiMeterApp(car);
assertEquals(900, taxiMeter.showPrice());
}
@Test
public void ifTheDistanceIsBetween16And100KmsAndTheCarTypeIsCompact_TheTotalFareShouldBe1100() {
Meter meter = new Meter(50);
CarType car = new Compact(meter);
TaxiMeterApp taxiMeter = new TaxiMeterApp(car);
assertEquals(1100, taxiMeter.showPrice());
}
@Test
public void ifTheDistanceIsBetween6And15KmsAndTheCarTypeIsCompact_TheTotalFareShouldBe390() {
Meter meter = new Meter(14);
CarType car = new Compact(meter);
TaxiMeterApp taxiMeter = new TaxiMeterApp(car);
assertEquals(390, taxiMeter.showPrice());
}
}
TaxiMeterApp: Shows the final price based on the type of car
public class TaxiMeterApp {
private CarType carType;
public TaxiMeterApp(CarType carType) {
this.carType = carType;
}
public int showPrice() {
Bill bill = new Bill(this.carType);
return bill.calculate();
}
}
CarType: Abstract class for Car type
public abstract class CarType {
protected int baseCharge;
private Meter meter;
public CarType(Meter meter) {
this.meter = meter;
}
public int getBaseCharge() {
return baseCharge;
}
public Meter getMeter() {
return meter;
}
}
Mini: Type of car
public class Mini extends CarType {
public Mini(Meter meter) {
super(meter);
this.baseCharge = 100;
}
}
Light: Type of car
public class Light extends CarType {
public Light(Meter meter) {
super(meter);
this.baseCharge = 200;
}
}
Compact: Type of car
public class Compact extends CarType {
public Compact(Meter meter) {
super(meter);
this.baseCharge = 300;
}
}
Meter:
public class Meter {
private int distance;
public int getDistance() {
return distance;
}
public Meter(int distance) {
this.distance = distance;
}
}
Bill: Class that generates the bill
public class Bill {
private CarType carType;
public Bill(CarType carType) {
this.carType = carType;
}
public int calculate() {
return this.carType.getBaseCharge() + calculatePriceBasedOn(this.carType.getMeter());
}
private int calculatePriceBasedOn(Meter meter) {
int distance = meter.getDistance();
if(distance > 100) {
return distance * 10;
} else if(distance <=100 && distance > 15) {
return (distance - 15) * 20 + 100;
} else if(distance > 5){
return (distance - 5) * 10;
}
return 0;
}
}
java object-oriented unit-testing
$endgroup$
add a comment |
$begingroup$
The following problem has been written using TDD. Please review the following:
- Unit tests
- OOP
- Design Principles (SOLID)
- Cleanliness and naming
- Anything that could make the program better.
Problem Statement:
Taxi Meter Company has to develop an app for their business. The app calculates the bill based on the car type and the distance covered. They have 3 different types of car and they charge the base price for the first 5 miles as
follows:
- Mini - $100
- Light - $200
- Compact - $300
After they cross 5 miles, the customer will be charged at 10$/mile
for the next 10 miles. Once they cross 15 miles, they will be charged at 20$/mile.
If the total distance covered is greater than 100 miles, they will be charged at 10$/mile for every mile (the total distance). Base price will be added by default in addition to the charges for the miles. Please refer sample input and output.
Sample Input:
- Mini, 10 Miles
- Light, 5 Miles
- Mini, 20 Miles
- Compact, 120 Miles
Sample Output:
- 150
- 200
- 300
- 1500
Test cases (Unit tests:)
public class TaxiMeterAppSpec {
@Test
public void givenTheDistanceCoveredForTheFirst5KmsAndTheCarTypeAsMiniTheBaseFareShouldBe100() {
Meter meter = new Meter(5);
CarType car = new Mini(meter);
TaxiMeterApp taxiMeter = new TaxiMeterApp(car);
assertEquals(100, taxiMeter.showPrice());
}
@Test
public void givenTheDistanceCoveredForTheFirst5KmsAndTheCarTypeAsLightTheBaseFareShouldBe200() {
Meter meter = new Meter(5);
CarType car = new Light(meter);
TaxiMeterApp taxiMeter = new TaxiMeterApp(car);
assertEquals(200, taxiMeter.showPrice());
}
@Test
public void givenTheDistanceCoveredForTheFirst5KmsAndTheCarTypeAsCompactTheBaseFareShouldBe300() {
Meter meter = new Meter(5);
CarType car = new Compact(meter);
TaxiMeterApp taxiMeter = new TaxiMeterApp(car);
assertEquals(300, taxiMeter.showPrice());
}
@Test
public void ifTheDistanceIsMoreThan100KmsAndTheCarTypeIsCompactTheTotalFareShouldBe1800() {
Meter meter = new Meter(150);
CarType car = new Compact(meter);
TaxiMeterApp taxiMeter = new TaxiMeterApp(car);
assertEquals(1800, taxiMeter.showPrice());
}
@Test
public void ifTheDistanceIsMoreThan100KmsAndTheCarTypeIsMiniTheTotalFareShouldBe1600() {
Meter meter = new Meter(150);
CarType car = new Mini(meter);
TaxiMeterApp taxiMeter = new TaxiMeterApp(car);
assertEquals(1600, taxiMeter.showPrice());
}
@Test
public void ifTheDistanceIsBetween16And100KmsAndTheCarTypeIsMiniTheTotalFareShouldBe900() {
Meter meter = new Meter(50);
CarType car = new Mini(meter);
TaxiMeterApp taxiMeter = new TaxiMeterApp(car);
assertEquals(900, taxiMeter.showPrice());
}
@Test
public void ifTheDistanceIsBetween16And100KmsAndTheCarTypeIsCompact_TheTotalFareShouldBe1100() {
Meter meter = new Meter(50);
CarType car = new Compact(meter);
TaxiMeterApp taxiMeter = new TaxiMeterApp(car);
assertEquals(1100, taxiMeter.showPrice());
}
@Test
public void ifTheDistanceIsBetween6And15KmsAndTheCarTypeIsCompact_TheTotalFareShouldBe390() {
Meter meter = new Meter(14);
CarType car = new Compact(meter);
TaxiMeterApp taxiMeter = new TaxiMeterApp(car);
assertEquals(390, taxiMeter.showPrice());
}
}
TaxiMeterApp: Shows the final price based on the type of car
public class TaxiMeterApp {
private CarType carType;
public TaxiMeterApp(CarType carType) {
this.carType = carType;
}
public int showPrice() {
Bill bill = new Bill(this.carType);
return bill.calculate();
}
}
CarType: Abstract class for Car type
public abstract class CarType {
protected int baseCharge;
private Meter meter;
public CarType(Meter meter) {
this.meter = meter;
}
public int getBaseCharge() {
return baseCharge;
}
public Meter getMeter() {
return meter;
}
}
Mini: Type of car
public class Mini extends CarType {
public Mini(Meter meter) {
super(meter);
this.baseCharge = 100;
}
}
Light: Type of car
public class Light extends CarType {
public Light(Meter meter) {
super(meter);
this.baseCharge = 200;
}
}
Compact: Type of car
public class Compact extends CarType {
public Compact(Meter meter) {
super(meter);
this.baseCharge = 300;
}
}
Meter:
public class Meter {
private int distance;
public int getDistance() {
return distance;
}
public Meter(int distance) {
this.distance = distance;
}
}
Bill: Class that generates the bill
public class Bill {
private CarType carType;
public Bill(CarType carType) {
this.carType = carType;
}
public int calculate() {
return this.carType.getBaseCharge() + calculatePriceBasedOn(this.carType.getMeter());
}
private int calculatePriceBasedOn(Meter meter) {
int distance = meter.getDistance();
if(distance > 100) {
return distance * 10;
} else if(distance <=100 && distance > 15) {
return (distance - 15) * 20 + 100;
} else if(distance > 5){
return (distance - 5) * 10;
}
return 0;
}
}
java object-oriented unit-testing
$endgroup$
add a comment |
$begingroup$
The following problem has been written using TDD. Please review the following:
- Unit tests
- OOP
- Design Principles (SOLID)
- Cleanliness and naming
- Anything that could make the program better.
Problem Statement:
Taxi Meter Company has to develop an app for their business. The app calculates the bill based on the car type and the distance covered. They have 3 different types of car and they charge the base price for the first 5 miles as
follows:
- Mini - $100
- Light - $200
- Compact - $300
After they cross 5 miles, the customer will be charged at 10$/mile
for the next 10 miles. Once they cross 15 miles, they will be charged at 20$/mile.
If the total distance covered is greater than 100 miles, they will be charged at 10$/mile for every mile (the total distance). Base price will be added by default in addition to the charges for the miles. Please refer sample input and output.
Sample Input:
- Mini, 10 Miles
- Light, 5 Miles
- Mini, 20 Miles
- Compact, 120 Miles
Sample Output:
- 150
- 200
- 300
- 1500
Test cases (Unit tests:)
public class TaxiMeterAppSpec {
@Test
public void givenTheDistanceCoveredForTheFirst5KmsAndTheCarTypeAsMiniTheBaseFareShouldBe100() {
Meter meter = new Meter(5);
CarType car = new Mini(meter);
TaxiMeterApp taxiMeter = new TaxiMeterApp(car);
assertEquals(100, taxiMeter.showPrice());
}
@Test
public void givenTheDistanceCoveredForTheFirst5KmsAndTheCarTypeAsLightTheBaseFareShouldBe200() {
Meter meter = new Meter(5);
CarType car = new Light(meter);
TaxiMeterApp taxiMeter = new TaxiMeterApp(car);
assertEquals(200, taxiMeter.showPrice());
}
@Test
public void givenTheDistanceCoveredForTheFirst5KmsAndTheCarTypeAsCompactTheBaseFareShouldBe300() {
Meter meter = new Meter(5);
CarType car = new Compact(meter);
TaxiMeterApp taxiMeter = new TaxiMeterApp(car);
assertEquals(300, taxiMeter.showPrice());
}
@Test
public void ifTheDistanceIsMoreThan100KmsAndTheCarTypeIsCompactTheTotalFareShouldBe1800() {
Meter meter = new Meter(150);
CarType car = new Compact(meter);
TaxiMeterApp taxiMeter = new TaxiMeterApp(car);
assertEquals(1800, taxiMeter.showPrice());
}
@Test
public void ifTheDistanceIsMoreThan100KmsAndTheCarTypeIsMiniTheTotalFareShouldBe1600() {
Meter meter = new Meter(150);
CarType car = new Mini(meter);
TaxiMeterApp taxiMeter = new TaxiMeterApp(car);
assertEquals(1600, taxiMeter.showPrice());
}
@Test
public void ifTheDistanceIsBetween16And100KmsAndTheCarTypeIsMiniTheTotalFareShouldBe900() {
Meter meter = new Meter(50);
CarType car = new Mini(meter);
TaxiMeterApp taxiMeter = new TaxiMeterApp(car);
assertEquals(900, taxiMeter.showPrice());
}
@Test
public void ifTheDistanceIsBetween16And100KmsAndTheCarTypeIsCompact_TheTotalFareShouldBe1100() {
Meter meter = new Meter(50);
CarType car = new Compact(meter);
TaxiMeterApp taxiMeter = new TaxiMeterApp(car);
assertEquals(1100, taxiMeter.showPrice());
}
@Test
public void ifTheDistanceIsBetween6And15KmsAndTheCarTypeIsCompact_TheTotalFareShouldBe390() {
Meter meter = new Meter(14);
CarType car = new Compact(meter);
TaxiMeterApp taxiMeter = new TaxiMeterApp(car);
assertEquals(390, taxiMeter.showPrice());
}
}
TaxiMeterApp: Shows the final price based on the type of car
public class TaxiMeterApp {
private CarType carType;
public TaxiMeterApp(CarType carType) {
this.carType = carType;
}
public int showPrice() {
Bill bill = new Bill(this.carType);
return bill.calculate();
}
}
CarType: Abstract class for Car type
public abstract class CarType {
protected int baseCharge;
private Meter meter;
public CarType(Meter meter) {
this.meter = meter;
}
public int getBaseCharge() {
return baseCharge;
}
public Meter getMeter() {
return meter;
}
}
Mini: Type of car
public class Mini extends CarType {
public Mini(Meter meter) {
super(meter);
this.baseCharge = 100;
}
}
Light: Type of car
public class Light extends CarType {
public Light(Meter meter) {
super(meter);
this.baseCharge = 200;
}
}
Compact: Type of car
public class Compact extends CarType {
public Compact(Meter meter) {
super(meter);
this.baseCharge = 300;
}
}
Meter:
public class Meter {
private int distance;
public int getDistance() {
return distance;
}
public Meter(int distance) {
this.distance = distance;
}
}
Bill: Class that generates the bill
public class Bill {
private CarType carType;
public Bill(CarType carType) {
this.carType = carType;
}
public int calculate() {
return this.carType.getBaseCharge() + calculatePriceBasedOn(this.carType.getMeter());
}
private int calculatePriceBasedOn(Meter meter) {
int distance = meter.getDistance();
if(distance > 100) {
return distance * 10;
} else if(distance <=100 && distance > 15) {
return (distance - 15) * 20 + 100;
} else if(distance > 5){
return (distance - 5) * 10;
}
return 0;
}
}
java object-oriented unit-testing
$endgroup$
The following problem has been written using TDD. Please review the following:
- Unit tests
- OOP
- Design Principles (SOLID)
- Cleanliness and naming
- Anything that could make the program better.
Problem Statement:
Taxi Meter Company has to develop an app for their business. The app calculates the bill based on the car type and the distance covered. They have 3 different types of car and they charge the base price for the first 5 miles as
follows:
- Mini - $100
- Light - $200
- Compact - $300
After they cross 5 miles, the customer will be charged at 10$/mile
for the next 10 miles. Once they cross 15 miles, they will be charged at 20$/mile.
If the total distance covered is greater than 100 miles, they will be charged at 10$/mile for every mile (the total distance). Base price will be added by default in addition to the charges for the miles. Please refer sample input and output.
Sample Input:
- Mini, 10 Miles
- Light, 5 Miles
- Mini, 20 Miles
- Compact, 120 Miles
Sample Output:
- 150
- 200
- 300
- 1500
Test cases (Unit tests:)
public class TaxiMeterAppSpec {
@Test
public void givenTheDistanceCoveredForTheFirst5KmsAndTheCarTypeAsMiniTheBaseFareShouldBe100() {
Meter meter = new Meter(5);
CarType car = new Mini(meter);
TaxiMeterApp taxiMeter = new TaxiMeterApp(car);
assertEquals(100, taxiMeter.showPrice());
}
@Test
public void givenTheDistanceCoveredForTheFirst5KmsAndTheCarTypeAsLightTheBaseFareShouldBe200() {
Meter meter = new Meter(5);
CarType car = new Light(meter);
TaxiMeterApp taxiMeter = new TaxiMeterApp(car);
assertEquals(200, taxiMeter.showPrice());
}
@Test
public void givenTheDistanceCoveredForTheFirst5KmsAndTheCarTypeAsCompactTheBaseFareShouldBe300() {
Meter meter = new Meter(5);
CarType car = new Compact(meter);
TaxiMeterApp taxiMeter = new TaxiMeterApp(car);
assertEquals(300, taxiMeter.showPrice());
}
@Test
public void ifTheDistanceIsMoreThan100KmsAndTheCarTypeIsCompactTheTotalFareShouldBe1800() {
Meter meter = new Meter(150);
CarType car = new Compact(meter);
TaxiMeterApp taxiMeter = new TaxiMeterApp(car);
assertEquals(1800, taxiMeter.showPrice());
}
@Test
public void ifTheDistanceIsMoreThan100KmsAndTheCarTypeIsMiniTheTotalFareShouldBe1600() {
Meter meter = new Meter(150);
CarType car = new Mini(meter);
TaxiMeterApp taxiMeter = new TaxiMeterApp(car);
assertEquals(1600, taxiMeter.showPrice());
}
@Test
public void ifTheDistanceIsBetween16And100KmsAndTheCarTypeIsMiniTheTotalFareShouldBe900() {
Meter meter = new Meter(50);
CarType car = new Mini(meter);
TaxiMeterApp taxiMeter = new TaxiMeterApp(car);
assertEquals(900, taxiMeter.showPrice());
}
@Test
public void ifTheDistanceIsBetween16And100KmsAndTheCarTypeIsCompact_TheTotalFareShouldBe1100() {
Meter meter = new Meter(50);
CarType car = new Compact(meter);
TaxiMeterApp taxiMeter = new TaxiMeterApp(car);
assertEquals(1100, taxiMeter.showPrice());
}
@Test
public void ifTheDistanceIsBetween6And15KmsAndTheCarTypeIsCompact_TheTotalFareShouldBe390() {
Meter meter = new Meter(14);
CarType car = new Compact(meter);
TaxiMeterApp taxiMeter = new TaxiMeterApp(car);
assertEquals(390, taxiMeter.showPrice());
}
}
TaxiMeterApp: Shows the final price based on the type of car
public class TaxiMeterApp {
private CarType carType;
public TaxiMeterApp(CarType carType) {
this.carType = carType;
}
public int showPrice() {
Bill bill = new Bill(this.carType);
return bill.calculate();
}
}
CarType: Abstract class for Car type
public abstract class CarType {
protected int baseCharge;
private Meter meter;
public CarType(Meter meter) {
this.meter = meter;
}
public int getBaseCharge() {
return baseCharge;
}
public Meter getMeter() {
return meter;
}
}
Mini: Type of car
public class Mini extends CarType {
public Mini(Meter meter) {
super(meter);
this.baseCharge = 100;
}
}
Light: Type of car
public class Light extends CarType {
public Light(Meter meter) {
super(meter);
this.baseCharge = 200;
}
}
Compact: Type of car
public class Compact extends CarType {
public Compact(Meter meter) {
super(meter);
this.baseCharge = 300;
}
}
Meter:
public class Meter {
private int distance;
public int getDistance() {
return distance;
}
public Meter(int distance) {
this.distance = distance;
}
}
Bill: Class that generates the bill
public class Bill {
private CarType carType;
public Bill(CarType carType) {
this.carType = carType;
}
public int calculate() {
return this.carType.getBaseCharge() + calculatePriceBasedOn(this.carType.getMeter());
}
private int calculatePriceBasedOn(Meter meter) {
int distance = meter.getDistance();
if(distance > 100) {
return distance * 10;
} else if(distance <=100 && distance > 15) {
return (distance - 15) * 20 + 100;
} else if(distance > 5){
return (distance - 5) * 10;
}
return 0;
}
}
java object-oriented unit-testing
java object-oriented unit-testing
edited Oct 23 '16 at 16:54
Vimde
asked Oct 23 '16 at 14:36
VimdeVimde
456
456
add a comment |
add a comment |
5 Answers
5
active
oldest
votes
$begingroup$
Code structure
I find it odd that a CarType
would know the distance it traveled. This class is supposed to model the type of the car, and this does not depend on the distance: a CarType
is the same whether you traveled 100 or 1000 miles with it. What really depend on the distance traveled is the price; therefore, I'd refactor the method showPrice
to take as parameter the meter
:
public int showPrice(Meter meter) {
Bill bill = new Bill(carType, meter);
return bill.calculate();
}
The idea is that when constructing a TaxiMeterApp
, you don't know in advance the distance the person is going to travel, only the car type they chose. And note that Bill
constructor would now take 2 parameters: the car type and the miles traveled, which is all it needs to derive the price.
A second point is the usage of an abstract class CarType
with multiple implementations. If you intend in the future to let users of the API create new CarType
s, this is a good solution; if not, consider having an enumeration, which will be simpler:
public enum CarType {
MINI(100), LIGHT(200), COMPACT(300);
private final int baseCharge;
CarType(int baseCharge) {
this.baseCharge = baseCharge;
}
public int getBaseCharge() {
return baseCharge;
}
}
The tests
There is a lot of repeat in your tests. They are all structured the same way, with just data changing:
@Test
public void givenTheDistanceCoveredForTheFirst5KmsAndTheCarTypeAsCompactTheBaseFareShouldBe300() {
Meter meter = new Meter(5);
CarType car = new Compact(meter);
TaxiMeterApp taxiMeter = new TaxiMeterApp(car);
assertEquals(300, taxiMeter.showPrice());
}
The only change with subsequent tests is that the type of the car, the distance and the expected result are different. Meaning the only changes are only data-changes. The same algorithm is tested on different data and it is asserted whether it behaves correctly or not. This leads to code duplication: if tomorrow you refactor the main algorithm, all of your tests will break and you'll need to revise each one to update them. Indeed, if you implement some of the changes discussed previously, you'll need to do just that.
The first possible solution is to refactor the structure of the test inside one method. You can create a method that takes the distance and the car type, and that would return the price:
private int priceForCarTypeAndMeter(int distance, CarType carType) {
Meter meter = new Meter(distance);
TaxiMeterApp taxiMeter = new TaxiMeterApp(carType);
return taxiMeter.showPrice(meter);
}
and then, all of tests are refactored to:
@Test
public void givenTheDistanceCoveredForTheFirst5KmsAndTheCarTypeAsMiniTheBaseFareShouldBe100() {
assertEquals(100, priceForCarTypeAndMeter(5, new Mini()));
}
without any code duplication as to how the price is actually derived from the distance and the car type.
Another approach in this case, is to have data-driven tests, which consists of a single test method, but which is invoked on multiple data. If you're using JUnit 4 or below, that can be done with parameterized tests and starting with JUnit 5, you can use dynamic tests.
$endgroup$
add a comment |
$begingroup$
Meter
I think this class totally useless since you only wrap an int
(for nothing ?)
Don't you think it's a bit redundant to write Meter meter = new Meter(5)
instead of int meter = 5
?
I would remove it.
CarType
As Tunaki said, I won't couple the distance into the CarType
, this has nothing to do with a CarType
.
Don't you have the feeling when you compare Mini
, Light
and Compact
that there's a lot of redundancy ? From my point of view, it's screaming for an enum since the only difference between all of that is the value of baseCharge
.
I would refactor all of that into the following:
public enum Car {
MINI(100), LIGHT(200), COMPACT(300);
private final int baseCharge;
Car(int baseCharge) {
this.baseCharge = baseCharge;
}
public int getBaseCharge() {
return baseCharge;
}
}
Bill
What does a Bill
need in order to compute a price ? A base charge and a distance. These two values must be provided in the bill constructor. The bill doesn't have to know about the Car
. Then, the implementation becomes (note that I have made this class immutable):
public final class Bill {
private final int baseCharge;
private final int meter;
public Bill(int baseCharge, int meter) {
this.baseCharge = baseCharge;
this.meter = meter;
}
public int calculate() {
return baseCharge + calculatePriceBasedOn(meter);
}
private int calculatePriceBasedOn(int distance) {
if (distance > 100) {
return distance * 10;
} else if (distance <= 100 && distance > 15) {
return (distance - 15) * 20 + 100;
} else if (distance > 5) {
return (distance - 5) * 10;
}
return 0;
}
}
TaxiMeterApp
This class also doesn't need to depend on Car
, the only useful information is the amount of the base charge.
One now takes meter
as a parameter since the distance is obviously not known when starting the taxi meter (the base charge is, however). I also removed the App from the class name because I think it has no value and only bring visual noise.
public final class TaxiMeter {
private final int baseCharge;
public TaxiMeter(int baseCharge) {
this.baseCharge = baseCharge;
}
public int showPrice(int meter) {
Bill bill = new Bill(baseCharge, meter);
return bill.calculate();
}
}
TaxiMeterAppSpec
I find your tests hard to understand for 2 reasons:
- Different naming conventions in method names
- A mixin between given, then and when
This is the kind of test I would write (I picked a convention for the names, you can choose another since it stays coherent). Given the previous refactors, the test implementations are more expressive now:
public class TaxiMeterSpec {
@Test
public void itShouldCost100WhenAMiniDrives5() {
TaxiMeter taxiMeter = new TaxiMeter(Car.MINI.getBaseCharge());
int price = taxiMeter.showPrice(5);
assertEquals(100, price);
}
@Test
public void itShouldCost200WhenALightDrives5() {
TaxiMeter taxiMeter = new TaxiMeter(Car.LIGHT.getBaseCharge());
int price = taxiMeter.showPrice(5);
assertEquals(200, price);
}
//and so on...
}
$endgroup$
add a comment |
$begingroup$
The biggest thing I see missing here, which would clean up the code considerably, is the addition of a TaxiTrip
class:
public class TaxiTrip
{
public TaxiTrip(CarType carType) {
// ...
}
public Bill calculateBill(Meter meter) {
// create Bill and calculate charges
}
}
This allows you to decouple the car type, meter and bill.
$endgroup$
add a comment |
$begingroup$
Your Class model does not look right to me.
You should create a new class when there is new behavior. Your CarType
extensions only differ in Properties. Therefore, creating classes is not justified.
The better way was to chenge the abstract CarType
- class to a regular class and all the different cars to enum
values:
public enum CarType {
Type(100),Light(200),Compact(300);
private final int baseCharge;
CarType(int baseCharge){
this.baseCharge = baseCharge;
}
public int getBaseCharge(){
return baseCharge;
}
}
class Car {
private final CarType carType;
private final Meter meter;
public CarType(Meter meter, CarType carType) {
this.meter = meter;
this.carType = carType;
}
// ...
}
$endgroup$
add a comment |
$begingroup$
public abstract class CarType {
protected int baseCharge;
private Meter meter;
public CarType(Meter meter) {
this.meter = meter;
}
This part has two smells:
Visibility of
baseCharge
: properties of a class should have least possible visibility. In your case there is no need to raise the visibility since there is a getter for it in this class already.
You use 2 different techniques to set the property values. This is called an odd ball solution. You'd better passed both values as constructor parameters:
public abstract class CarType {
private int baseCharge;
private Meter meter;
public CarType(Meter meter, int baseCharge) {
this.meter = meter;
this.baseCharge = baseCharge;
}
$endgroup$
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
return StackExchange.using("mathjaxEditing", function () {
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
});
});
}, "mathjax-editing");
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "196"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f145008%2ftaxi-meter-app-business-logic-using-tdd%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
5 Answers
5
active
oldest
votes
5 Answers
5
active
oldest
votes
active
oldest
votes
active
oldest
votes
$begingroup$
Code structure
I find it odd that a CarType
would know the distance it traveled. This class is supposed to model the type of the car, and this does not depend on the distance: a CarType
is the same whether you traveled 100 or 1000 miles with it. What really depend on the distance traveled is the price; therefore, I'd refactor the method showPrice
to take as parameter the meter
:
public int showPrice(Meter meter) {
Bill bill = new Bill(carType, meter);
return bill.calculate();
}
The idea is that when constructing a TaxiMeterApp
, you don't know in advance the distance the person is going to travel, only the car type they chose. And note that Bill
constructor would now take 2 parameters: the car type and the miles traveled, which is all it needs to derive the price.
A second point is the usage of an abstract class CarType
with multiple implementations. If you intend in the future to let users of the API create new CarType
s, this is a good solution; if not, consider having an enumeration, which will be simpler:
public enum CarType {
MINI(100), LIGHT(200), COMPACT(300);
private final int baseCharge;
CarType(int baseCharge) {
this.baseCharge = baseCharge;
}
public int getBaseCharge() {
return baseCharge;
}
}
The tests
There is a lot of repeat in your tests. They are all structured the same way, with just data changing:
@Test
public void givenTheDistanceCoveredForTheFirst5KmsAndTheCarTypeAsCompactTheBaseFareShouldBe300() {
Meter meter = new Meter(5);
CarType car = new Compact(meter);
TaxiMeterApp taxiMeter = new TaxiMeterApp(car);
assertEquals(300, taxiMeter.showPrice());
}
The only change with subsequent tests is that the type of the car, the distance and the expected result are different. Meaning the only changes are only data-changes. The same algorithm is tested on different data and it is asserted whether it behaves correctly or not. This leads to code duplication: if tomorrow you refactor the main algorithm, all of your tests will break and you'll need to revise each one to update them. Indeed, if you implement some of the changes discussed previously, you'll need to do just that.
The first possible solution is to refactor the structure of the test inside one method. You can create a method that takes the distance and the car type, and that would return the price:
private int priceForCarTypeAndMeter(int distance, CarType carType) {
Meter meter = new Meter(distance);
TaxiMeterApp taxiMeter = new TaxiMeterApp(carType);
return taxiMeter.showPrice(meter);
}
and then, all of tests are refactored to:
@Test
public void givenTheDistanceCoveredForTheFirst5KmsAndTheCarTypeAsMiniTheBaseFareShouldBe100() {
assertEquals(100, priceForCarTypeAndMeter(5, new Mini()));
}
without any code duplication as to how the price is actually derived from the distance and the car type.
Another approach in this case, is to have data-driven tests, which consists of a single test method, but which is invoked on multiple data. If you're using JUnit 4 or below, that can be done with parameterized tests and starting with JUnit 5, you can use dynamic tests.
$endgroup$
add a comment |
$begingroup$
Code structure
I find it odd that a CarType
would know the distance it traveled. This class is supposed to model the type of the car, and this does not depend on the distance: a CarType
is the same whether you traveled 100 or 1000 miles with it. What really depend on the distance traveled is the price; therefore, I'd refactor the method showPrice
to take as parameter the meter
:
public int showPrice(Meter meter) {
Bill bill = new Bill(carType, meter);
return bill.calculate();
}
The idea is that when constructing a TaxiMeterApp
, you don't know in advance the distance the person is going to travel, only the car type they chose. And note that Bill
constructor would now take 2 parameters: the car type and the miles traveled, which is all it needs to derive the price.
A second point is the usage of an abstract class CarType
with multiple implementations. If you intend in the future to let users of the API create new CarType
s, this is a good solution; if not, consider having an enumeration, which will be simpler:
public enum CarType {
MINI(100), LIGHT(200), COMPACT(300);
private final int baseCharge;
CarType(int baseCharge) {
this.baseCharge = baseCharge;
}
public int getBaseCharge() {
return baseCharge;
}
}
The tests
There is a lot of repeat in your tests. They are all structured the same way, with just data changing:
@Test
public void givenTheDistanceCoveredForTheFirst5KmsAndTheCarTypeAsCompactTheBaseFareShouldBe300() {
Meter meter = new Meter(5);
CarType car = new Compact(meter);
TaxiMeterApp taxiMeter = new TaxiMeterApp(car);
assertEquals(300, taxiMeter.showPrice());
}
The only change with subsequent tests is that the type of the car, the distance and the expected result are different. Meaning the only changes are only data-changes. The same algorithm is tested on different data and it is asserted whether it behaves correctly or not. This leads to code duplication: if tomorrow you refactor the main algorithm, all of your tests will break and you'll need to revise each one to update them. Indeed, if you implement some of the changes discussed previously, you'll need to do just that.
The first possible solution is to refactor the structure of the test inside one method. You can create a method that takes the distance and the car type, and that would return the price:
private int priceForCarTypeAndMeter(int distance, CarType carType) {
Meter meter = new Meter(distance);
TaxiMeterApp taxiMeter = new TaxiMeterApp(carType);
return taxiMeter.showPrice(meter);
}
and then, all of tests are refactored to:
@Test
public void givenTheDistanceCoveredForTheFirst5KmsAndTheCarTypeAsMiniTheBaseFareShouldBe100() {
assertEquals(100, priceForCarTypeAndMeter(5, new Mini()));
}
without any code duplication as to how the price is actually derived from the distance and the car type.
Another approach in this case, is to have data-driven tests, which consists of a single test method, but which is invoked on multiple data. If you're using JUnit 4 or below, that can be done with parameterized tests and starting with JUnit 5, you can use dynamic tests.
$endgroup$
add a comment |
$begingroup$
Code structure
I find it odd that a CarType
would know the distance it traveled. This class is supposed to model the type of the car, and this does not depend on the distance: a CarType
is the same whether you traveled 100 or 1000 miles with it. What really depend on the distance traveled is the price; therefore, I'd refactor the method showPrice
to take as parameter the meter
:
public int showPrice(Meter meter) {
Bill bill = new Bill(carType, meter);
return bill.calculate();
}
The idea is that when constructing a TaxiMeterApp
, you don't know in advance the distance the person is going to travel, only the car type they chose. And note that Bill
constructor would now take 2 parameters: the car type and the miles traveled, which is all it needs to derive the price.
A second point is the usage of an abstract class CarType
with multiple implementations. If you intend in the future to let users of the API create new CarType
s, this is a good solution; if not, consider having an enumeration, which will be simpler:
public enum CarType {
MINI(100), LIGHT(200), COMPACT(300);
private final int baseCharge;
CarType(int baseCharge) {
this.baseCharge = baseCharge;
}
public int getBaseCharge() {
return baseCharge;
}
}
The tests
There is a lot of repeat in your tests. They are all structured the same way, with just data changing:
@Test
public void givenTheDistanceCoveredForTheFirst5KmsAndTheCarTypeAsCompactTheBaseFareShouldBe300() {
Meter meter = new Meter(5);
CarType car = new Compact(meter);
TaxiMeterApp taxiMeter = new TaxiMeterApp(car);
assertEquals(300, taxiMeter.showPrice());
}
The only change with subsequent tests is that the type of the car, the distance and the expected result are different. Meaning the only changes are only data-changes. The same algorithm is tested on different data and it is asserted whether it behaves correctly or not. This leads to code duplication: if tomorrow you refactor the main algorithm, all of your tests will break and you'll need to revise each one to update them. Indeed, if you implement some of the changes discussed previously, you'll need to do just that.
The first possible solution is to refactor the structure of the test inside one method. You can create a method that takes the distance and the car type, and that would return the price:
private int priceForCarTypeAndMeter(int distance, CarType carType) {
Meter meter = new Meter(distance);
TaxiMeterApp taxiMeter = new TaxiMeterApp(carType);
return taxiMeter.showPrice(meter);
}
and then, all of tests are refactored to:
@Test
public void givenTheDistanceCoveredForTheFirst5KmsAndTheCarTypeAsMiniTheBaseFareShouldBe100() {
assertEquals(100, priceForCarTypeAndMeter(5, new Mini()));
}
without any code duplication as to how the price is actually derived from the distance and the car type.
Another approach in this case, is to have data-driven tests, which consists of a single test method, but which is invoked on multiple data. If you're using JUnit 4 or below, that can be done with parameterized tests and starting with JUnit 5, you can use dynamic tests.
$endgroup$
Code structure
I find it odd that a CarType
would know the distance it traveled. This class is supposed to model the type of the car, and this does not depend on the distance: a CarType
is the same whether you traveled 100 or 1000 miles with it. What really depend on the distance traveled is the price; therefore, I'd refactor the method showPrice
to take as parameter the meter
:
public int showPrice(Meter meter) {
Bill bill = new Bill(carType, meter);
return bill.calculate();
}
The idea is that when constructing a TaxiMeterApp
, you don't know in advance the distance the person is going to travel, only the car type they chose. And note that Bill
constructor would now take 2 parameters: the car type and the miles traveled, which is all it needs to derive the price.
A second point is the usage of an abstract class CarType
with multiple implementations. If you intend in the future to let users of the API create new CarType
s, this is a good solution; if not, consider having an enumeration, which will be simpler:
public enum CarType {
MINI(100), LIGHT(200), COMPACT(300);
private final int baseCharge;
CarType(int baseCharge) {
this.baseCharge = baseCharge;
}
public int getBaseCharge() {
return baseCharge;
}
}
The tests
There is a lot of repeat in your tests. They are all structured the same way, with just data changing:
@Test
public void givenTheDistanceCoveredForTheFirst5KmsAndTheCarTypeAsCompactTheBaseFareShouldBe300() {
Meter meter = new Meter(5);
CarType car = new Compact(meter);
TaxiMeterApp taxiMeter = new TaxiMeterApp(car);
assertEquals(300, taxiMeter.showPrice());
}
The only change with subsequent tests is that the type of the car, the distance and the expected result are different. Meaning the only changes are only data-changes. The same algorithm is tested on different data and it is asserted whether it behaves correctly or not. This leads to code duplication: if tomorrow you refactor the main algorithm, all of your tests will break and you'll need to revise each one to update them. Indeed, if you implement some of the changes discussed previously, you'll need to do just that.
The first possible solution is to refactor the structure of the test inside one method. You can create a method that takes the distance and the car type, and that would return the price:
private int priceForCarTypeAndMeter(int distance, CarType carType) {
Meter meter = new Meter(distance);
TaxiMeterApp taxiMeter = new TaxiMeterApp(carType);
return taxiMeter.showPrice(meter);
}
and then, all of tests are refactored to:
@Test
public void givenTheDistanceCoveredForTheFirst5KmsAndTheCarTypeAsMiniTheBaseFareShouldBe100() {
assertEquals(100, priceForCarTypeAndMeter(5, new Mini()));
}
without any code duplication as to how the price is actually derived from the distance and the car type.
Another approach in this case, is to have data-driven tests, which consists of a single test method, but which is invoked on multiple data. If you're using JUnit 4 or below, that can be done with parameterized tests and starting with JUnit 5, you can use dynamic tests.
answered Oct 23 '16 at 17:16
TunakiTunaki
9,06012244
9,06012244
add a comment |
add a comment |
$begingroup$
Meter
I think this class totally useless since you only wrap an int
(for nothing ?)
Don't you think it's a bit redundant to write Meter meter = new Meter(5)
instead of int meter = 5
?
I would remove it.
CarType
As Tunaki said, I won't couple the distance into the CarType
, this has nothing to do with a CarType
.
Don't you have the feeling when you compare Mini
, Light
and Compact
that there's a lot of redundancy ? From my point of view, it's screaming for an enum since the only difference between all of that is the value of baseCharge
.
I would refactor all of that into the following:
public enum Car {
MINI(100), LIGHT(200), COMPACT(300);
private final int baseCharge;
Car(int baseCharge) {
this.baseCharge = baseCharge;
}
public int getBaseCharge() {
return baseCharge;
}
}
Bill
What does a Bill
need in order to compute a price ? A base charge and a distance. These two values must be provided in the bill constructor. The bill doesn't have to know about the Car
. Then, the implementation becomes (note that I have made this class immutable):
public final class Bill {
private final int baseCharge;
private final int meter;
public Bill(int baseCharge, int meter) {
this.baseCharge = baseCharge;
this.meter = meter;
}
public int calculate() {
return baseCharge + calculatePriceBasedOn(meter);
}
private int calculatePriceBasedOn(int distance) {
if (distance > 100) {
return distance * 10;
} else if (distance <= 100 && distance > 15) {
return (distance - 15) * 20 + 100;
} else if (distance > 5) {
return (distance - 5) * 10;
}
return 0;
}
}
TaxiMeterApp
This class also doesn't need to depend on Car
, the only useful information is the amount of the base charge.
One now takes meter
as a parameter since the distance is obviously not known when starting the taxi meter (the base charge is, however). I also removed the App from the class name because I think it has no value and only bring visual noise.
public final class TaxiMeter {
private final int baseCharge;
public TaxiMeter(int baseCharge) {
this.baseCharge = baseCharge;
}
public int showPrice(int meter) {
Bill bill = new Bill(baseCharge, meter);
return bill.calculate();
}
}
TaxiMeterAppSpec
I find your tests hard to understand for 2 reasons:
- Different naming conventions in method names
- A mixin between given, then and when
This is the kind of test I would write (I picked a convention for the names, you can choose another since it stays coherent). Given the previous refactors, the test implementations are more expressive now:
public class TaxiMeterSpec {
@Test
public void itShouldCost100WhenAMiniDrives5() {
TaxiMeter taxiMeter = new TaxiMeter(Car.MINI.getBaseCharge());
int price = taxiMeter.showPrice(5);
assertEquals(100, price);
}
@Test
public void itShouldCost200WhenALightDrives5() {
TaxiMeter taxiMeter = new TaxiMeter(Car.LIGHT.getBaseCharge());
int price = taxiMeter.showPrice(5);
assertEquals(200, price);
}
//and so on...
}
$endgroup$
add a comment |
$begingroup$
Meter
I think this class totally useless since you only wrap an int
(for nothing ?)
Don't you think it's a bit redundant to write Meter meter = new Meter(5)
instead of int meter = 5
?
I would remove it.
CarType
As Tunaki said, I won't couple the distance into the CarType
, this has nothing to do with a CarType
.
Don't you have the feeling when you compare Mini
, Light
and Compact
that there's a lot of redundancy ? From my point of view, it's screaming for an enum since the only difference between all of that is the value of baseCharge
.
I would refactor all of that into the following:
public enum Car {
MINI(100), LIGHT(200), COMPACT(300);
private final int baseCharge;
Car(int baseCharge) {
this.baseCharge = baseCharge;
}
public int getBaseCharge() {
return baseCharge;
}
}
Bill
What does a Bill
need in order to compute a price ? A base charge and a distance. These two values must be provided in the bill constructor. The bill doesn't have to know about the Car
. Then, the implementation becomes (note that I have made this class immutable):
public final class Bill {
private final int baseCharge;
private final int meter;
public Bill(int baseCharge, int meter) {
this.baseCharge = baseCharge;
this.meter = meter;
}
public int calculate() {
return baseCharge + calculatePriceBasedOn(meter);
}
private int calculatePriceBasedOn(int distance) {
if (distance > 100) {
return distance * 10;
} else if (distance <= 100 && distance > 15) {
return (distance - 15) * 20 + 100;
} else if (distance > 5) {
return (distance - 5) * 10;
}
return 0;
}
}
TaxiMeterApp
This class also doesn't need to depend on Car
, the only useful information is the amount of the base charge.
One now takes meter
as a parameter since the distance is obviously not known when starting the taxi meter (the base charge is, however). I also removed the App from the class name because I think it has no value and only bring visual noise.
public final class TaxiMeter {
private final int baseCharge;
public TaxiMeter(int baseCharge) {
this.baseCharge = baseCharge;
}
public int showPrice(int meter) {
Bill bill = new Bill(baseCharge, meter);
return bill.calculate();
}
}
TaxiMeterAppSpec
I find your tests hard to understand for 2 reasons:
- Different naming conventions in method names
- A mixin between given, then and when
This is the kind of test I would write (I picked a convention for the names, you can choose another since it stays coherent). Given the previous refactors, the test implementations are more expressive now:
public class TaxiMeterSpec {
@Test
public void itShouldCost100WhenAMiniDrives5() {
TaxiMeter taxiMeter = new TaxiMeter(Car.MINI.getBaseCharge());
int price = taxiMeter.showPrice(5);
assertEquals(100, price);
}
@Test
public void itShouldCost200WhenALightDrives5() {
TaxiMeter taxiMeter = new TaxiMeter(Car.LIGHT.getBaseCharge());
int price = taxiMeter.showPrice(5);
assertEquals(200, price);
}
//and so on...
}
$endgroup$
add a comment |
$begingroup$
Meter
I think this class totally useless since you only wrap an int
(for nothing ?)
Don't you think it's a bit redundant to write Meter meter = new Meter(5)
instead of int meter = 5
?
I would remove it.
CarType
As Tunaki said, I won't couple the distance into the CarType
, this has nothing to do with a CarType
.
Don't you have the feeling when you compare Mini
, Light
and Compact
that there's a lot of redundancy ? From my point of view, it's screaming for an enum since the only difference between all of that is the value of baseCharge
.
I would refactor all of that into the following:
public enum Car {
MINI(100), LIGHT(200), COMPACT(300);
private final int baseCharge;
Car(int baseCharge) {
this.baseCharge = baseCharge;
}
public int getBaseCharge() {
return baseCharge;
}
}
Bill
What does a Bill
need in order to compute a price ? A base charge and a distance. These two values must be provided in the bill constructor. The bill doesn't have to know about the Car
. Then, the implementation becomes (note that I have made this class immutable):
public final class Bill {
private final int baseCharge;
private final int meter;
public Bill(int baseCharge, int meter) {
this.baseCharge = baseCharge;
this.meter = meter;
}
public int calculate() {
return baseCharge + calculatePriceBasedOn(meter);
}
private int calculatePriceBasedOn(int distance) {
if (distance > 100) {
return distance * 10;
} else if (distance <= 100 && distance > 15) {
return (distance - 15) * 20 + 100;
} else if (distance > 5) {
return (distance - 5) * 10;
}
return 0;
}
}
TaxiMeterApp
This class also doesn't need to depend on Car
, the only useful information is the amount of the base charge.
One now takes meter
as a parameter since the distance is obviously not known when starting the taxi meter (the base charge is, however). I also removed the App from the class name because I think it has no value and only bring visual noise.
public final class TaxiMeter {
private final int baseCharge;
public TaxiMeter(int baseCharge) {
this.baseCharge = baseCharge;
}
public int showPrice(int meter) {
Bill bill = new Bill(baseCharge, meter);
return bill.calculate();
}
}
TaxiMeterAppSpec
I find your tests hard to understand for 2 reasons:
- Different naming conventions in method names
- A mixin between given, then and when
This is the kind of test I would write (I picked a convention for the names, you can choose another since it stays coherent). Given the previous refactors, the test implementations are more expressive now:
public class TaxiMeterSpec {
@Test
public void itShouldCost100WhenAMiniDrives5() {
TaxiMeter taxiMeter = new TaxiMeter(Car.MINI.getBaseCharge());
int price = taxiMeter.showPrice(5);
assertEquals(100, price);
}
@Test
public void itShouldCost200WhenALightDrives5() {
TaxiMeter taxiMeter = new TaxiMeter(Car.LIGHT.getBaseCharge());
int price = taxiMeter.showPrice(5);
assertEquals(200, price);
}
//and so on...
}
$endgroup$
Meter
I think this class totally useless since you only wrap an int
(for nothing ?)
Don't you think it's a bit redundant to write Meter meter = new Meter(5)
instead of int meter = 5
?
I would remove it.
CarType
As Tunaki said, I won't couple the distance into the CarType
, this has nothing to do with a CarType
.
Don't you have the feeling when you compare Mini
, Light
and Compact
that there's a lot of redundancy ? From my point of view, it's screaming for an enum since the only difference between all of that is the value of baseCharge
.
I would refactor all of that into the following:
public enum Car {
MINI(100), LIGHT(200), COMPACT(300);
private final int baseCharge;
Car(int baseCharge) {
this.baseCharge = baseCharge;
}
public int getBaseCharge() {
return baseCharge;
}
}
Bill
What does a Bill
need in order to compute a price ? A base charge and a distance. These two values must be provided in the bill constructor. The bill doesn't have to know about the Car
. Then, the implementation becomes (note that I have made this class immutable):
public final class Bill {
private final int baseCharge;
private final int meter;
public Bill(int baseCharge, int meter) {
this.baseCharge = baseCharge;
this.meter = meter;
}
public int calculate() {
return baseCharge + calculatePriceBasedOn(meter);
}
private int calculatePriceBasedOn(int distance) {
if (distance > 100) {
return distance * 10;
} else if (distance <= 100 && distance > 15) {
return (distance - 15) * 20 + 100;
} else if (distance > 5) {
return (distance - 5) * 10;
}
return 0;
}
}
TaxiMeterApp
This class also doesn't need to depend on Car
, the only useful information is the amount of the base charge.
One now takes meter
as a parameter since the distance is obviously not known when starting the taxi meter (the base charge is, however). I also removed the App from the class name because I think it has no value and only bring visual noise.
public final class TaxiMeter {
private final int baseCharge;
public TaxiMeter(int baseCharge) {
this.baseCharge = baseCharge;
}
public int showPrice(int meter) {
Bill bill = new Bill(baseCharge, meter);
return bill.calculate();
}
}
TaxiMeterAppSpec
I find your tests hard to understand for 2 reasons:
- Different naming conventions in method names
- A mixin between given, then and when
This is the kind of test I would write (I picked a convention for the names, you can choose another since it stays coherent). Given the previous refactors, the test implementations are more expressive now:
public class TaxiMeterSpec {
@Test
public void itShouldCost100WhenAMiniDrives5() {
TaxiMeter taxiMeter = new TaxiMeter(Car.MINI.getBaseCharge());
int price = taxiMeter.showPrice(5);
assertEquals(100, price);
}
@Test
public void itShouldCost200WhenALightDrives5() {
TaxiMeter taxiMeter = new TaxiMeter(Car.LIGHT.getBaseCharge());
int price = taxiMeter.showPrice(5);
assertEquals(200, price);
}
//and so on...
}
edited Oct 26 '16 at 12:16
answered Oct 26 '16 at 11:45
SpottedSpotted
54928
54928
add a comment |
add a comment |
$begingroup$
The biggest thing I see missing here, which would clean up the code considerably, is the addition of a TaxiTrip
class:
public class TaxiTrip
{
public TaxiTrip(CarType carType) {
// ...
}
public Bill calculateBill(Meter meter) {
// create Bill and calculate charges
}
}
This allows you to decouple the car type, meter and bill.
$endgroup$
add a comment |
$begingroup$
The biggest thing I see missing here, which would clean up the code considerably, is the addition of a TaxiTrip
class:
public class TaxiTrip
{
public TaxiTrip(CarType carType) {
// ...
}
public Bill calculateBill(Meter meter) {
// create Bill and calculate charges
}
}
This allows you to decouple the car type, meter and bill.
$endgroup$
add a comment |
$begingroup$
The biggest thing I see missing here, which would clean up the code considerably, is the addition of a TaxiTrip
class:
public class TaxiTrip
{
public TaxiTrip(CarType carType) {
// ...
}
public Bill calculateBill(Meter meter) {
// create Bill and calculate charges
}
}
This allows you to decouple the car type, meter and bill.
$endgroup$
The biggest thing I see missing here, which would clean up the code considerably, is the addition of a TaxiTrip
class:
public class TaxiTrip
{
public TaxiTrip(CarType carType) {
// ...
}
public Bill calculateBill(Meter meter) {
// create Bill and calculate charges
}
}
This allows you to decouple the car type, meter and bill.
answered Oct 26 '16 at 21:01
Greg BurghardtGreg Burghardt
5,026620
5,026620
add a comment |
add a comment |
$begingroup$
Your Class model does not look right to me.
You should create a new class when there is new behavior. Your CarType
extensions only differ in Properties. Therefore, creating classes is not justified.
The better way was to chenge the abstract CarType
- class to a regular class and all the different cars to enum
values:
public enum CarType {
Type(100),Light(200),Compact(300);
private final int baseCharge;
CarType(int baseCharge){
this.baseCharge = baseCharge;
}
public int getBaseCharge(){
return baseCharge;
}
}
class Car {
private final CarType carType;
private final Meter meter;
public CarType(Meter meter, CarType carType) {
this.meter = meter;
this.carType = carType;
}
// ...
}
$endgroup$
add a comment |
$begingroup$
Your Class model does not look right to me.
You should create a new class when there is new behavior. Your CarType
extensions only differ in Properties. Therefore, creating classes is not justified.
The better way was to chenge the abstract CarType
- class to a regular class and all the different cars to enum
values:
public enum CarType {
Type(100),Light(200),Compact(300);
private final int baseCharge;
CarType(int baseCharge){
this.baseCharge = baseCharge;
}
public int getBaseCharge(){
return baseCharge;
}
}
class Car {
private final CarType carType;
private final Meter meter;
public CarType(Meter meter, CarType carType) {
this.meter = meter;
this.carType = carType;
}
// ...
}
$endgroup$
add a comment |
$begingroup$
Your Class model does not look right to me.
You should create a new class when there is new behavior. Your CarType
extensions only differ in Properties. Therefore, creating classes is not justified.
The better way was to chenge the abstract CarType
- class to a regular class and all the different cars to enum
values:
public enum CarType {
Type(100),Light(200),Compact(300);
private final int baseCharge;
CarType(int baseCharge){
this.baseCharge = baseCharge;
}
public int getBaseCharge(){
return baseCharge;
}
}
class Car {
private final CarType carType;
private final Meter meter;
public CarType(Meter meter, CarType carType) {
this.meter = meter;
this.carType = carType;
}
// ...
}
$endgroup$
Your Class model does not look right to me.
You should create a new class when there is new behavior. Your CarType
extensions only differ in Properties. Therefore, creating classes is not justified.
The better way was to chenge the abstract CarType
- class to a regular class and all the different cars to enum
values:
public enum CarType {
Type(100),Light(200),Compact(300);
private final int baseCharge;
CarType(int baseCharge){
this.baseCharge = baseCharge;
}
public int getBaseCharge(){
return baseCharge;
}
}
class Car {
private final CarType carType;
private final Meter meter;
public CarType(Meter meter, CarType carType) {
this.meter = meter;
this.carType = carType;
}
// ...
}
edited Oct 26 '16 at 22:05
200_success
130k16153417
130k16153417
answered Oct 25 '16 at 21:29
Timothy TruckleTimothy Truckle
4,903416
4,903416
add a comment |
add a comment |
$begingroup$
public abstract class CarType {
protected int baseCharge;
private Meter meter;
public CarType(Meter meter) {
this.meter = meter;
}
This part has two smells:
Visibility of
baseCharge
: properties of a class should have least possible visibility. In your case there is no need to raise the visibility since there is a getter for it in this class already.
You use 2 different techniques to set the property values. This is called an odd ball solution. You'd better passed both values as constructor parameters:
public abstract class CarType {
private int baseCharge;
private Meter meter;
public CarType(Meter meter, int baseCharge) {
this.meter = meter;
this.baseCharge = baseCharge;
}
$endgroup$
add a comment |
$begingroup$
public abstract class CarType {
protected int baseCharge;
private Meter meter;
public CarType(Meter meter) {
this.meter = meter;
}
This part has two smells:
Visibility of
baseCharge
: properties of a class should have least possible visibility. In your case there is no need to raise the visibility since there is a getter for it in this class already.
You use 2 different techniques to set the property values. This is called an odd ball solution. You'd better passed both values as constructor parameters:
public abstract class CarType {
private int baseCharge;
private Meter meter;
public CarType(Meter meter, int baseCharge) {
this.meter = meter;
this.baseCharge = baseCharge;
}
$endgroup$
add a comment |
$begingroup$
public abstract class CarType {
protected int baseCharge;
private Meter meter;
public CarType(Meter meter) {
this.meter = meter;
}
This part has two smells:
Visibility of
baseCharge
: properties of a class should have least possible visibility. In your case there is no need to raise the visibility since there is a getter for it in this class already.
You use 2 different techniques to set the property values. This is called an odd ball solution. You'd better passed both values as constructor parameters:
public abstract class CarType {
private int baseCharge;
private Meter meter;
public CarType(Meter meter, int baseCharge) {
this.meter = meter;
this.baseCharge = baseCharge;
}
$endgroup$
public abstract class CarType {
protected int baseCharge;
private Meter meter;
public CarType(Meter meter) {
this.meter = meter;
}
This part has two smells:
Visibility of
baseCharge
: properties of a class should have least possible visibility. In your case there is no need to raise the visibility since there is a getter for it in this class already.
You use 2 different techniques to set the property values. This is called an odd ball solution. You'd better passed both values as constructor parameters:
public abstract class CarType {
private int baseCharge;
private Meter meter;
public CarType(Meter meter, int baseCharge) {
this.meter = meter;
this.baseCharge = baseCharge;
}
edited Oct 27 '16 at 16:39
Jamal♦
30.3k11119227
30.3k11119227
answered Oct 25 '16 at 21:42
Timothy TruckleTimothy Truckle
4,903416
4,903416
add a comment |
add a comment |
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f145008%2ftaxi-meter-app-business-logic-using-tdd%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown