Refactoring code using Strategy Pattern - oop

I have a GiftCouponPayment class. It has a business strategy logic which can change frequently - GetCouponValue(). At present the logic is “The coupon value should be considered as zero when the Coupon Number is less than 2000”. In a future business strategy it may change as “The coupon value should be considered as zero when the Coupon Issued Date is less than 1/1/2000”. It can change to any such strategies based on the managing department of the company.
How can we refactor the GiftCouponPayment class using Strategy pattern so that the class need not be changed when the strategy for GetCouponValue method?
UPDATE: After analyzing the responsibilities, I feel, "GiftCoupon" will be a better name for "GiftCouponPayment" class.
C# CODE
public int GetCouponValue()
{
int effectiveValue = -1;
if (CouponNumber < 2000)
{
effectiveValue = 0;
}
else
{
effectiveValue = CouponValue;
}
return effectiveValue;
}
READING
Strategy Pattern - multiple return types/values

GiftCouponPayment class should pass GiftCoupon to different strategy classes. So your strategy interface (CouponValueStrategy) should contain a method:
int getCouponValue(GiftCoupon giftCoupon)
Since each Concrete strategy implementing CouponValueStrategy has access to GiftCoupon, each can implement an algorithm based on Coupon number or Coupon date etc.

You can inject a "coupon value policy" into the coupon object itself and call upon it to compute the coupon value. In such cases, it is acceptable to pass this into the policy so that the policy can ask the coupon for its required attributes (such as coupon number):
public interface ICouponValuePolicy
{
int ComputeCouponValue(GiftCouponPayment couponPayment);
}
public class GiftCouponPayment
{
public ICouponValuePolicy CouponValuePolicy {
get;
set;
}
public int GetCouponValue()
{
return CouponValuePolicy.ComputeCouponValue(this);
}
}
Also, it seems like your GiftCouponPayment is really responsible for two things (the payment and the gift coupon). It might make sense to extract a GiftCoupon class that contains CouponNumber, CouponValue and GetCouponValue(), and refer to this from the GiftCouponPayment.

When your business - logic changes, it's quite natural that your code will have to change as well.
You could perhaps opt to move the expiration-detection logic into a specification class:
public class CouponIsExpiredBasedOnNumber : ICouponIsExpiredSpecification
{
public bool IsExpired( Coupon c )
{
if( c.CouponNumber < 2000 )
return true;
else
return false;
}
}
public class CouponIsExpiredBasedOnDate : ICouponIsExpiredSpecification
{
public readonly DateTime expirationDate = new DateTime (2000, 1, 1);
public bool IsExpired( Coupon c )
{
if( c.Date < expirationDate )
return true;
else
return false;
}
}
public class Coupon
{
public int GetCouponValue()
{
ICouponIsExpiredSpecification expirationRule = GetExpirationRule();
if( expirationRule.IsExpired(this) )
return 0;
else
return this.Value;
}
}
The question you should ask yourself: is it necessary to make it this complex right now ? Can't you make it as simple as possible to satisfy current needs, and refactor it later, when the expiration-rule indeed changes ?

The behavior that you wish to be dynamic is the coupon calculation - which can dependent on any number of things: coupon date, coupon number, etc. I think that a provider pattern would be more appropriate, to inject a service class which calculates the coupon value.
The essence of this is moving the business logic outside of the GiftCouponPayment class, and using a class I'll call "CouponCalculator" to encapsulate the business logic. This class uses an interface.
interface ICouponCalculator
{
int Calculate (GiftCouponPayment payment);
}
public class CouponCalculator : ICouponCalculator
{
public int Calculate (GiftCouponPayment payment)
{
if (payment.CouponNumber < 2000)
{
return 0;
}
else
{
return payment.CouponValue;
}
}
}
Now that you have this interface and class, add a property to the GiftCouponPayment class, then modify your original GetCouponValue() method:
public class GiftCouponPayment
{
public int CouponNumber;
public int CouponValue;
public ICouponCalculator Calculator { get; set; }
public int GetCouponValue()
{
return Calculator.Calculate(this);
}
}
When you construct the GiftCouponPayment class, you will assign the Calculator property:
var payment = new GiftCouponPayment() { Calculator = new CouponCalculator(); }
var val = payment.GetCouponValue(); // uses CouponCalculator class to get value
If this seems like a lot of work just to move the calculation logic outside of the GiftCouponPayment class, well, it is! But if this is your requirement, it does provide several things:
1. You won't need to change the GiftCouponPayment class to adjust the calculation logic.
2. You could create additional classes that implement ICalculator, and a factory pattern to decide which class to inject into GiftCouponPayment when it is constructed. This speaks more to your original desire for a "strategy" pattern - as this would be useful if the logic becomes very complex.

Related

Best Practice for OOP function with multiple possible control flows

In my project, I have this special function that does needs to evaluate the following:
State -- represented by an enum -- and there are about 6 different states
Left Argument
Right Argument
Left and Right arguments are represented by strings, but their values can be the following:
"_" (a wildcard)
"1" (an integer string)
"abc" (a normal string)
So as you can see, to cover all every single possibility, there's about 2 * 3 * 6 = 36 different logics to evaluate and of course, using if-else in one giant function will not be feasible at all. I have encapsulated the above 3 input into an object that I'll pass to my function.
How would one try to use OOP to solve this. Would it make sense to have 6 different subclasses of the main State class with an evaluate() method, and then in their respective methods, I have if else statements to check:
if left & right arg are wildcards, do something
if left is number, right is string, do something else
Repeat for all the valid combinations in each State subclass
This feels like the right direction, but it also feels like theres alot of duplicate logic (for example check if both args are wildcards, or both strings etc.) for all 6 subclasses. Then my thought is to abstract it abit more and make another subclass:
For each state subclass, I have stateWithTwoWildCards, statewithTwoString etc.
But I feel like this is going way overboard and over-engineering and being "too" specific (I get that this technically adheres tightly to SOLID, especially SRP and OCP concepts). Any thoughts on this?
Possibly something like template method pattern can be useful in this case. I.e. you will encapsulate all the checking logic in the base State.evaluate method and create several methods which subclasses will override. Something along this lines:
class StateBase
def evaluate():
if(bothWildcards)
evalBothWildcards()
else if(bothStrings)
evalBothStrings()
else if ...
def evalBothWildcards():
...
def evalBothStrings():
...
Where evalBothWildcards, evalBothStrings, etc. will be overloaded in inheritors.
there's about 2 * 3 * 6 = 36 different logics to evaluate
We can apply divide and conquer technique.
you have 6 states. It is possible to use Chain of Responibility pattern here to choose appropriate state handler
when desired state handler is found, then we can apply desired function. The appropriate function can be considered as strategy. So it is a place where Strategy pattern can be applied.
we can separate strategies by appropriate states and put them in simple factory to get desired strategy by key.
This is what we will do. So let's see it more thoroughly.
Chain of responsibility pattern
If you have a lot if else statements, it is possible to use Chain of Responsibility pattern. As wiki says about Chain of Responsibility:
The chain-of-responsibility pattern is a behavioral design pattern
consisting of a source of command objects and a series of processing
objects. Each processing object contains logic that defines the
types of command objects that it can handle; the rest are passed to
the next processing object in the chain. A mechanism also exists for
adding new processing objects to the end of this chain
So let's dive in code. Let me show an example via C#.
So this is our Argument class which has Left and Right operands:
public class Arguments
{
public string Left { get; private set; }
public string Right { get; private set; }
public MyState MyState { get; private set; }
public MyKey MyKey => new MyKey(MyState, Left);
public Arguments(string left, string right, MyState myState)
{
Left = left;
Right = right;
MyState = myState;
}
}
And this is your 6 states:
public enum MyState
{
One, Two, Three, Four, Five, Six
}
This is start of Decorator pattern. This is an abstraction of StateHandler which defines behaviour to to set next handler:
public abstract class StateHandler
{
public abstract MyState State { get; }
private StateHandler _nextStateHandler;
public void SetSuccessor(StateHandler nextStateHandler)
{
_nextStateHandler = nextStateHandler;
}
public virtual IDifferentLogicStrategy Execute(Arguments arguments)
{
if (_nextStateHandler != null)
return _nextStateHandler.Execute(arguments);
return null;
}
}
and its concrete implementations of StateHandler:
public class OneStateHandler : StateHandler
{
public override MyState State => MyState.One;
public override IDifferentLogicStrategy Execute(Arguments arguments)
{
if (arguments.MyState == State)
return new StrategyStateFactory().GetInstanceByMyKey(arguments.MyKey);
return base.Execute(arguments);
}
}
public class TwoStateHandler : StateHandler
{
public override MyState State => MyState.Two;
public override IDifferentLogicStrategy Execute(Arguments arguments)
{
if (arguments.MyState == State)
return new StrategyStateFactory().GetInstanceByMyKey(arguments.MyKey);
return base.Execute(arguments);
}
}
and the third state handler looks like this:
public class ThreeStateHandler : StateHandler
{
public override MyState State => MyState.Three;
public override IDifferentLogicStrategy Execute(Arguments arguments)
{
if (arguments.MyState == State)
return new StrategyStateFactory().GetInstanceByMyKey(arguments.MyKey);
return base.Execute(arguments);
}
}
Strategy pattern
Let's pay attention to the following row of code:
return new StrategyStateFactory().GetInstanceByMyKey(arguments.MyKey);
The above code is an example of using Strategy pattern. We have different ways or strategies to handle
your cases. Let me show a code of strategies of evaluation of your expressions.
This is an abstraction of strategy:
public interface IDifferentLogicStrategy
{
string Evaluate(Arguments arguments);
}
And its concrete implementations:
public class StrategyWildCardStateOne : IDifferentLogicStrategy
{
public string Evaluate(Arguments arguments)
{
// your logic here to evaluate "_" (a wildcard)
return "StrategyWildCardStateOne";
}
}
public class StrategyIntegerStringStateOne : IDifferentLogicStrategy
{
public string Evaluate(Arguments arguments)
{
// your logic here to evaluate "1" (an integer string)
return "StrategyIntegerStringStateOne";
}
}
And the third strategy:
public class StrategyNormalStringStateOne : IDifferentLogicStrategy
{
public string Evaluate(Arguments arguments)
{
// your logic here to evaluate "abc" (a normal string)
return "StrategyNormalStringStateOne";
}
}
Simple factory
There is no pattern like simple factory. However, it is a place where we can get instances of strategies by key. So by doing this we avoided to use multiple if else statements to choose correct strategy.
So, we need a place where we can store strategies by state and argument value. At first, let's create MyKey struct. It will have help us to differentiate State and arguments:
public struct MyKey
{
public readonly MyState MyState { get; }
public readonly string ArgumentValue { get; } // your three cases: "_",
// an integer string, a normal string
public MyKey(MyState myState, string argumentValue)
{
MyState = myState;
ArgumentValue = argumentValue;
}
public override bool Equals([NotNullWhen(true)] object? obj)
{
return obj is MyKey mys
&& mys.MyState == MyState
&& mys.ArgumentValue == ArgumentValue;
}
public override int GetHashCode()
{
unchecked // Overflow is fine, just wrap
{
int hash = 17;
hash = hash * 23 + MyState.GetHashCode();
hash = hash * 23 + ArgumentValue.GetHashCode();
return hash;
}
}
}
and then we can create a simple factory:
public class StrategyStateFactory
{
private Dictionary<MyKey, IDifferentLogicStrategy>
_differentLogicStrategyByStateAndValue =
new Dictionary<MyKey, IDifferentLogicStrategy>()
{
{ new MyKey(MyState.One, "_"), new StrategyWildCardStateOne() },
{ new MyKey(MyState.One, "intString"),
new StrategyIntegerStringStateOne() },
{ new MyKey(MyState.One, "normalString"),
new StrategyNormalStringStateOne() }
};
public IDifferentLogicStrategy GetInstanceByMyKey(MyKey myKey)
{
return _differentLogicStrategyByStateAndValue[myKey];
}
}
So we've written our strategies and we've stored these strategies in simple factory StrategyStateFactory.
Then we need to check the above implementation:
StateHandler chain = new OneStateHandler();
StateHandler secondStateHandler = new TwoStateHandler();
StateHandler thirdStateHandler = new ThreeStateHandler();
chain.SetSuccessor(secondStateHandler);
secondStateHandler.SetSuccessor(thirdStateHandler);
Arguments arguments = new Arguments("_", "_", MyState.One);
IDifferentLogicStrategy differentLogicStrategy = chain.Execute(arguments);
string evaluatedResult =
differentLogicStrategy.Evaluate(arguments); // output: "StrategyWildCardStateOne"
I believe I gave basic idea how it can be done.

Polymorphism on a REST service

I am trying to clean and refactor my service code which currently looks like this-
public void generateBalance(Receipt receipt) {
if (receipt.getType().equals(X) && receipt.getRegion.equals(EMEA)) {
// do something to the receipt that's passed
} else if (receiptType.equals(Y)) {
// do something to the receipt
} else if (receipt.getRegion.equals(APAC) {
// call an external API and update the receipt
}....
...
// finally
dataStore.save(receipt);
Basically there's a bunch of conditionals that are in this main service which look for certain fields in the object that is being passed. Either it's the type or the region.
I was looking to use this design pattern- https://www.refactoring.com/catalog/replaceConditionalWithPolymorphism.html
However, I am not sure how this would work for a service class. Currently my REST handler calls this particular service. Also how can I do polymorphism for both the "receiptType" and "region"?
Is there a way I can just do all the updates to the receipt once in different services, then finally save the receipt at one location? (maybe a base class?) I am really confused on how to start. TIA!
If your classes should have the same behaviour, then it becomes pretty simple to use polymorpism. The pattern is called as Strategy. Let me show an example.
At first we need to use enum. If you do not have enum, then you can create a method which will return enum value based on your conditions:
if (receipt.getType().equals(X) && receipt.getRegion.equals(EMEA)) // other
// code is omitted for the brevity
So enum will look like this:
public enum ReceiptType
{
Emea, Y, Apac
}
Then we need an abstract class which will describe behaviour for derived classes:
public abstract class ActionReceipt
{
public abstract string Do();
}
And our derived classes will look this:
public class ActionReceiptEmea : ActionReceipt
{
public override string Do()
{
return "I am Emea";
}
}
public class ActionReceiptY : ActionReceipt
{
public override string Do()
{
return "I am Y";
}
}
public class ActionReceiptApac : ActionReceipt
{
public override string Do()
{
return "I am Apac";
}
}
Moreover, we need a factory which will create derived classes based on enum. So we can use Factory pattern with a slight modification:
public class ActionReceiptFactory
{
private Dictionary<ReceiptType, ActionReceipt> _actionReceiptByType =
new Dictionary<ReceiptType, ActionReceipt>
{
{
ReceiptType.Apac, new ActionReceiptApac()
},
{
ReceiptType.Emea, new ActionReceiptEmea()
},
{
ReceiptType.Y, new ActionReceiptY()
}
};
public ActionReceipt GetInstanceByReceiptType(ReceiptType receiptType) =>
_actionReceiptByType[receiptType];
}
And then polymorpism in action will look like this:
void DoSomething(ReceiptType receiptType)
{
ActionReceiptFactory actionReceiptFactory = new ActionReceiptFactory();
ActionReceipt receipt =
actionReceiptFactory.GetInstanceByReceiptType(receiptType);
string someDoing = receipt.Do(); // Output: "I am Emea"
}
UPDATE:
You can create some helper method which will return enum value based on
your logic of region and receiptType:
public class ReceiptTypeHelper
{
public ReceiptType Get(ActionReceipt actionReceipt)
{
if (actionReceipt.GetType().Equals("Emea"))
return ReceiptType.Emea;
else if (actionReceipt.GetType().Equals("Y"))
return ReceiptType.Y;
return ReceiptType.Apac;
}
}
and you can call it like this:
void DoSomething()
{
ReceiptTypeHelper receiptTypeHelper = new ReceiptTypeHelper();
ReceiptType receiptType = receiptTypeHelper
.Get(new ActionReceiptEmea());
ActionReceiptFactory actionReceiptFactory = new
ActionReceiptFactory();
ActionReceipt receipt =
actionReceiptFactory.GetInstanceByReceiptType(receiptType);
string someDoing = receipt.Do(); // Output: "I am Emea"
}

spring-data-rest return multiple projections at once (compose projections)

Is there possibility to compose multiple projections? Is it best approach for my use case?
I have these projections for CarInBazar class for example:
SimpleCarInList
WidgetForHotSale
NumberOfItemViews
FullCarData
CarMainImage
CarMainImageIconSize
CarAdditionalImages
And frontend is now making some UI page, which requires some of these projections.
Should I do:
frontend will do multiple requests for same resource with different projections
Implement Projection for each screen of frontend (duplicating things like NumberOfItemViews calculation,...)
Use inheritance and make specific projections utilizing extends keyword. i.e.:
#Projection(name = "screen-dashboardHome", types = {CarInBazar.class})
public interface DashboardHomeProjectionForCarInBazar extends SimpleCarInList,
WidgetForHotSale, CarMainImageIconSize {
}
Is there any possibility to request more projections at once? It could be than rendered in UI using provided profiles perhaps.
EDIT: as requested, providing projection definition example:
import org.springframework.data.rest.core.config.Projection;
import java.awt.*;
import java.util.Date;
#Projection(name = "CarMainImage", types = {CarInBazar.class})
public interface CarMainImage {
Date getLastUpdateDate();
Image getMainImage();
default String getMainImageAdditionalInformation() {
final var updated = this.getLastUpdateDate().getTime();
final var created = this.getCreatedAtDate().getTime();
if (created >= (updated - 10 minutes)) {
return "some business logic on not published fields";
} else {
return "could happen not only in spel";
}
}
}
Many projections does not contain any business logic and are only filtering fields.
Using multiple projections seems to not be supported (or widely supported or easy). Using suggestion from #Aivaras comment I have used this approach:
Repository code with custom JPQL query:
#Repository
#RepositoryRestResource
#Transactional(readOnly = true)
public interface SomeRepository extends PagingAndSortingRepository<Some, Long> {
Page<Some> findByNameContaining(String namePart, Pageable pageable);
#Query("select new sk.qpp.documents.projections.SomeCustomViewByQuery(s.name, s.startDate, s.endDate, s.goLiveDate, 42) from Some s where s.id = :id")
Optional<SomeCustomViewByQuery> getByIdProjectedForSpecialScreen(Long id);
}
And class SomeCustomViewByQuery is just simple DTO like thing. Using lombok it can look like:
#Value
public class SomeCustomViewByQuery {
private String name;
private Date startDate;
private Date endDate;
private Date goLiveDate;
// TODO make SomeHealth to be enum and specific logic behind it.
String getSomeHealth() {
final var start = this.getStartDate().getTime();
final var end = this.getEndDate().getTime();
final var goLive = this.getGoLiveDate().getTime();
final var now = System.currentTimeMillis();
if (now < start) {
return "not started yet";
} else {
if (now < end) {
return "work in progress";
} else {
if (now < goLive) {
return "passed end, but before goLive";
} else {
return "something after goLive time";
}
}
}
}
private int unicornsCount;
}
This way, I can make hand-crafted query (JQPL) and also create custom DTO instance. It is handy, where I need to do some joins to other tables with aggregation (count, avg, max, min, ...) and other things, which are better done on database side.

How to create an optional module signature type in OCaml/Reason

I am trying to follow the builder design pattern using modules in Reason.
I have the following type:
type userBuilderType = {
mutable name: string,
};
As well as signature type:
module type UserBuilderType = {
let name: string;
};
I am passing the UserBuilderType signature type as a functor to the BuilderPattern:
module BuilderPattern = fun(Builder: UserBuilderType) => {
let builder = {
name: "",
};
let setName = builder.name = Builder.name;
let getName () => builder.name;
};
I am then passing the appropriate value as a module doing the following:
module SetOfMixedPairs = BuilderPattern({
let name = "asd";
});
However, in order for this builder design pattern, to truly be a builder design pattern, the signature type will need to be optional. I am struggling as how to do so. If I were for instance, to edit the signature type to be empty:
module type UserBuilderType = {};
The compiler will complain: Unbound value Builder.name. Any suggestions as to how to make the signature type optional, are more than welcome. My thanks as always.
Full code can be seen here.
First of all, usually you can't implement a design pattern using some mechanism of a language, as design patterns are not expressible directly in the language type system and syntax. Design patterns describe a particular methodology for solving recurring problems in software development. As soon, as a language provides a mechanism to express a design pattern directly, this is no longer considered a design pattern. Thus something that is a design pattern in one language, becomes a mechanism in another language. For example, a loop in assembly language is a design pattern, though in most modern languages it's a syntactic construct. A presence of design patterns usually indicates a lack of expressivity of a particular language or programming paradigm. Though, no matter how expressive your language, there always be abstractions, that can't be implemented directly using the language mechanisms.
You should also understand that GoF design patterns were written with the OOP paradigm in mind with peculiarities and limitations of OOP languages of that time. So they are not always applicable or even needed in OCaml/Reason or any other languages with parametric polymorphism and first-class functions.
In particular, the problem that the Builder pattern is trying to solve is an absence of first-class constructors and parametric polymorphism. Since we have both in Reason, we are usually not bothered with designing complex hierarchies of types. Another limitation of OOP is an absence of algebraic data types, that are ideal language mechanism for implementing complex compound data structures such as abstract syntax trees (expression parsing trees) and so on.
With all this said, you can still use the Builder pattern in Reason, but most likely you don't actually need it, as the language provides much better and more expressible mechanisms for solving your problem. Let's use the SportsCarBuilder code from Wikipedia, as our working example,
/// <summary>
/// Represents a product created by the builder
/// </summary>
public class Car
{
public string Make { get; }
public string Model { get; }
public int NumDoors { get; }
public string Colour { get; }
public Car(string make, string model, string colour, int numDoors)
{
Make = make;
Model = model;
Colour = colour;
NumDoors = numDoors;
}
}
/// <summary>
/// The builder abstraction
/// </summary>
public interface ICarBuilder
{
string Colour { get; set; }
int NumDoors { get; set; }
Car GetResult();
}
/// <summary>
/// Concrete builder implementation
/// </summary>
public class FerrariBuilder : ICarBuilder
{
public string Colour { get; set; }
public int NumDoors { get; set; }
public Car GetResult()
{
return NumDoors == 2 ? new Car("Ferrari", "488 Spider", Colour, NumDoors) : null;
}
}
/// <summary>
/// The director
/// </summary>
public class SportsCarBuildDirector
{
private ICarBuilder _builder;
public SportsCarBuildDirector(ICarBuilder builder)
{
_builder = builder;
}
public void Construct()
{
_builder.Colour = "Red";
_builder.NumDoors = 2;
}
}
public class Client
{
public void DoSomethingWithCars()
{
var builder = new FerrariBuilder();
var director = new SportsCarBuildDirector(builder);
director.Construct();
Car myRaceCar = builder.GetResult();
}
}
We will provide a one-to-one translation from C# to Reason, to show the direct counterparts of C# mechanisms in Reason. Note, we will not build an idiomatic Reason code, people will unlikely follow the Builder Pattern in Reason.
The Car class defines an interface of a build product. We will represent it as a module type in Reason:
module type Car = {
type t;
let make : string;
let model : string;
let numDoors : int;
let colour: string;
let create : (~make:string, ~model:string, ~numDoors:int, ~colour:string) => t;
};
We decided to make the car type abstract (letting an implementor to choose a particular implementation, whether it would be a record, an object, or maybe an key to a SQL database of cars.
We will now define a corresponding interface for the car builder:
module type CarBuilder = {
type t;
type car;
let setColour : (t,string) => unit;
let getColour : t => string;
let setNumDoors : (t,int) => unit;
let getNumDoors : t => int;
let getResult : t => car;
}
Now let's implement a concrete builder. Since we decided to make the car type abstract, we need to parametrize our concrete builder with the car type. In OCaml/Reason, when you need something to parametrize with a type, you usually use functors.
module FerariBuilder = (Car: Car) => {
type t = {
mutable numDoors: int,
mutable colour: string
};
exception BadFerrari;
let setColour = (builder, colour) => builder.colour = colour;
let getColour = (builder) => builder.colour;
let setNumDoors = (builder, n) => builder.numDoors = n;
let getNumDoors = (builder) => builder.numDoors;
let getResult = (builder) =>
if (builder.numDoors == 2) {
Car.create(~make="Ferrari", ~model="488 Spider",
~numDoors=2, ~colour=builder.colour)
} else {
raise(BadFerrari)
};
};
And finally, let's implement a director.
module Director = (Car: Car, Builder: CarBuilder with type car = Car.t) => {
let construct = (builder) => {
Builder.setColour(builder, "red");
Builder.setNumDoors(builder, 2)
};
};
I will let you implementing the user code as an exercise. Hint, you need to start with a concrete implementation of the Car interface. You may look and play with the code (including the OCaml and Javascript version) at Try Reason.

Replacing a series of if statements with an interface-based approach

so I am trying to write a discounts method that will apply discount(s) on a product.
The current vanilla code goes like so:
void ApplyDiscount(List<DiscountRule> discountRules, Product objProduct)
{
foreach (var discountRule in discountRules)
{
// this is a very simple way of deciding on the available discounts
if (discountRule.Type==DiscountType.Percent)
{
// Process for percentage discount
}
if (discountRule.Type==DiscountType.Free)
{
// Process for flat discount
}
// and so on , there are like 5 more types,
// not mentioned here for the case of brevity.
}
}
What this method does is take a list of discount rules and apply on the product.
The discount rules are fetched by executing a SP # the server and that returns the
available discounts for that product.
The code review for this resulted in the following comment:
Please use an interface based approach and try to get rid of the IFs!
I can get rid of the IFs but they will be replaced by SWITCH.
How do I go about using an interface?
May be this question is not constructive, but I would want to know if some OOPs gurus here can guide me in writing this better.
Regards.
An interface / virtual-dispatch approach might look something like this:
// First we loosely define how a "discount" can be used.
// This could also be an abstract class, if common base-class
// functionality is desired.
public interface IDiscount
{
// This is called to apply this discount to a particular product
void ApplyDiscount(Product product);
}
// Here's one implementation that applies a percentage discount
public class PercentDiscount : IDiscount
{
private decimal m_percent;
public PercentDiscount(decimal percent) {
m_percent = percent;
}
#region IDiscount implementation
public void ApplyDiscount(Product product) {
product.Price -= product.Price * m_discount;
}
#endregion
}
// Here's another implementation that makes a product free
public class FreeDiscount : IDiscount
{
public FreeDiscount() {
}
#region IDiscount implementation
public void ApplyDiscount(Product product) {
product.Price = 0;
}
#endregion
}
public class SomeClass {
// Now applying the discounts becomes much simpler! Note that this function
// takes a collection of IDiscounts, and applies them in a consistent way,
// by just calling IDiscount.ApplyDiscount()
void ApplyDiscounts(IEnumerable<IDiscount> discounts, Product product) {
foreach (var discount in discounts) {
discount.ApplyDiscount(product);
}
}
}
Note that I also changed ApplyDiscounts to take an IEnumerable<T> instead of List<T>. This allows any arbitrary collection type to be passed, and also doesn't allow the function to inadvertently modify the collection.