wojciech-dabrowski
wojciech-dabrowski
wojc.dabrowski@gmail.com
Refactoring to Patterns
Wojciech Dąbrowski
THings That annoy us
Conditionals
public decimal CalculateCost()
{
var totalCost = _itemCost;
if (_wishListItemType == WishListItemType.EducationMaterial)
{
if (_vendorsWithDiscounts.ContainsKey(_vendorName))
{
var discountAmount = totalCost * _vendorsWithDiscounts[_vendorName];
totalCost -= discountAmount;
}
}
if (_wishListItemType == WishListItemType.ELearningLicense)
{
var duration = _endDate - _startDate;
if (duration.HasValue && duration.Value.Days > 180)
{
totalCost *= 0.8m;
}
}
if (_wishListItemType == WishListItemType.Exam)
{
if (_approachNumber == 2)
{
totalCost /= 2;
}
else if (_approachNumber == 3)
{
totalCost /= 4;
}
else if (_approachNumber > 3)
{
totalCost = 0;
}
}
if (_wishListItemType == WishListItemType.Conference)
{
if (_location == LocationType.Foreign)
{
totalCost *= 0.7m;
}
}
if (_wishListItemType == WishListItemType.Conference
|| _wishListItemType == WishListItemType.Training
|| _wishListItemType == WishListItemType.Exam)
{
if (_sideCosts.IncludeAccommodationCost)
{
totalCost += _sideCosts.AccommodationCost;
}
if (_sideCosts.IncludeDailyAllowanceCost)
{
totalCost += _sideCosts.DailyAllowanceCost;
}
if (_sideCosts.IncludeTransportCost)
{
totalCost += _sideCosts.TransportCost;
}
}
return totalCost;
}
https://dreamlandia.com/l/labyrinth.html
Duplications
https://yarn.co/yarn-clip/526155f5-6e9b-4733-8a59-d17705e62cdc
public decimal CalculateCost()
{
var totalCost = _itemCost;
if (_wishListItemType == WishListItemType.EducationMaterial)
{
if (_vendorsWithDiscounts.ContainsKey(_vendorName))
{
var discountAmount = totalCost * _vendorsWithDiscounts[_vendorName];
totalCost -= discountAmount;
}
}
if (_wishListItemType == WishListItemType.ELearningLicense)
{
var duration = _endDate - _startDate;
if (duration.HasValue && duration.Value.Days > 180)
{
totalCost *= 0.8m;
}
}
if (_wishListItemType == WishListItemType.Exam)
{
if (_approachNumber == 2)
{
totalCost /= 2;
}
else if (_approachNumber == 3)
{
totalCost /= 4;
}
else if (_approachNumber > 3)
{
totalCost = 0;
}
}
if (_wishListItemType == WishListItemType.Conference)
{
if (_location == LocationType.Foreign)
{
totalCost *= 0.7m;
}
}
if (_wishListItemType == WishListItemType.Conference
|| _wishListItemType == WishListItemType.Training
|| _wishListItemType == WishListItemType.Exam)
{
if (_sideCosts.IncludeAccommodationCost)
{
totalCost += _sideCosts.AccommodationCost;
}
if (_sideCosts.IncludeDailyAllowanceCost)
{
totalCost += _sideCosts.DailyAllowanceCost;
}
if (_sideCosts.IncludeTransportCost)
{
totalCost += _sideCosts.TransportCost;
}
}
return totalCost;
}
Refactoring
'a change made to the internal structure of software to make it easier to understand and cheaper to modify without changing its observable behavior.'
Refactoring: Improving the Design of Existing Code
Martin Fowler
'a change made to the internal structure of software to make it easier to understand and cheaper to modify without changing its observable behavior.'
Refactoring: Improving the Design of Existing Code
Martin Fowler
Introduction to the domain
1. Introduction
2. Conditionals
3. Conditionals, part 2
4. Duplications
5. Summary
public class WishListItem
{
...
public WishListItemStatus Status { get; private set; }
public decimal AcceptBy(User user)
{
...
}
public decimal RejectBy(User user)
{
...
}
public decimal StartRealizationBy(User user)
{
...
}
public decimal FinishRealizationBy(User user)
{
...
}
...
}
public void AcceptBy(User user)
{
if (Status != WishListItemStatus.Requested &&
Status != WishListItemStatus.RequestedToDirector)
{
throw new CannotAcceptWishListItemWithCurrentStatusException(Status);
}
if (Status == WishListItemStatus.Requested)
{
if (!user.IsLeaderOf(_owner))
{
throw new UserDoesNotHavePermissionToAcceptRequestedWishListItemException();
}
if (ShouldBeRequestedToDirector())
{
Status = WishListItemStatus.RequestedToDirector;
}
else
{
Status = WishListItemStatus.Accepted;
}
return;
}
if (!user.IsDirectorOf(_owner))
{
throw new UserDoesNotHavePermissionToAcceptRequestedWishListItemException();
}
Status = WishListItemStatus.Accepted;
}
public void RejectBy(User user)
{
if (Status != WishListItemStatus.Requested &&
Status != WishListItemStatus.RequestedToDirector)
{
throw new CannotRejectWishListItemWithCurrentStatusException(Status);
}
if (Status == WishListItemStatus.Requested)
{
if (!user.IsLeaderOf(_owner))
{
throw new UserDoesNotHavePermissionToRejectRequestedWishListItemException();
}
Status = WishListItemStatus.Rejected;
return;
}
if (!user.IsDirectorOf(_owner))
{
throw new UserDoesNotHavePermissionToRejectRequestedWishListItemException();
}
Status = WishListItemStatus.Rejected;
}
public void StartRealizationBy(User user)
{
if (Status != WishListItemStatus.Accepted)
{
throw new CannotStartWishListItemRealizationWithCurrentStatusException(Status);
}
if (!user.IsSupervisor)
{
throw new UserDoesNotHavePermissionToStartWishListItemRealizationException();
}
Status = WishListItemStatus.InRealization;
}
public void FinishRealizationBy(User user)
{
if (Status != WishListItemStatus.InRealization)
{
throw new CannotFinishWishListItemRealizationWithCurrentStatusException(Status);
}
if (!user.IsSupervisor)
{
throw new UserDoesNotHavePermissionToFinishWishListItemRealizationException();
}
if (!_areCostsInvoiced)
{
throw new CannotFinishWishListItemRealizationWithNotInvoicedException();
}
Status = WishListItemStatus.Realized;
}
public void AcceptBy(User user)
{
if (Status != WishListItemStatus.Requested &&
Status != WishListItemStatus.RequestedToDirector)
{
throw new CannotAcceptWishListItemWithCurrentStatusException(Status);
}
if (Status == WishListItemStatus.Requested)
{
if (!user.IsLeaderOf(_owner))
{
throw new UserDoesNotHavePermissionToAcceptRequestedWishListItemException();
}
if (ShouldBeRequestedToDirector())
{
Status = WishListItemStatus.RequestedToDirector;
}
else
{
Status = WishListItemStatus.Accepted;
}
return;
}
if (!user.IsDirectorOf(_owner))
{
throw new UserDoesNotHavePermissionToAcceptRequestedWishListItemException();
}
Status = WishListItemStatus.Accepted;
}
State pattern
Step 1
Replace status (state) - field or property with class.
Step 1
Replace status (state) - field or property with class.
public class WishListItem
{
...
public WishListItemStatus Status { get; private set; }
public decimal AcceptBy(User user)
{
...
}
public decimal RejectBy(User user)
{
...
}
public decimal StartRealizationBy(User user)
{
...
}
public decimal FinishRealizationBy(User user)
{
...
}
...
}
Step 1
Replace status (state) - field or property with class.
internal class WishListItemState
{
internal static WishListItemState Requested =
new WishListItemState(WishListItemStatus.Requested);
internal static WishListItemState RequestedToDirector =
new WishListItemState(WishListItemStatus.RequestedToDirector);
internal static WishListItemState Rejected =
new WishListItemState(WishListItemStatus.Rejected);
internal static WishListItemState Accepted =
new WishListItemState(WishListItemStatus.Accepted);
internal static WishListItemState InRealization =
new WishListItemState(WishListItemStatus.InRealization);
internal static WishListItemState Realized =
new WishListItemState(WishListItemStatus.Realized);
private WishListItemState(
WishListItemStatus status)
{
Status = status;
}
internal WishListItemStatus Status { get; }
...
}
Step 1
Replace status (state) - field or property with class.
public class WishListItem
{
...
private WishListItemState State { get; set; }
public WishListItemStatus Status => State.Status;
public void AcceptBy(User user)
{
if (Status != WishListItemStatus.Requested &&
Status != WishListItemStatus.RequestedToDirector)
{
throw new CannotAcceptWishListItemWithCurrentStatusException(Status);
}
...
State = WishListItemState.Accepted;
}
...
}
Step 1
Replace status (state) - field or property with class.
Compile code and run tests.
Step 2
Create subclass for every state.
Step 2
Create subclass for every state.
internal class WishListItemRequested : WishListItemState
{
internal override WishListItemStatus Status => WishListItemStatus.Requested;
}
internal class WishListItemRequestedToDirector : WishListItemState
{
internal override WishListItemStatus Status => WishListItemStatus.RequestedToDirector;
}
internal class WishListItemAccepted : WishListItemState
{
internal override WishListItemStatus Status => WishListItemStatus.Accepted;
}
internal class WishListItemRejected : WishListItemState
{
internal override WishListItemStatus Status => WishListItemStatus.Rejected;
}
internal class WishListItemInRealization : WishListItemState
{
internal override WishListItemStatus Status => WishListItemStatus.InRealization;
}
internal class WishListItemRealized : WishListItemState
{
internal override WishListItemStatus Status => WishListItemStatus.Realized;
}
Step 2
Create subclass for every state.
internal abstract class WishListItemState
{
internal static WishListItemState Requested =
new WishListItemRequested();
internal static WishListItemState RequestedToDirector =
new WishListItemRequestedToDirector();
internal static WishListItemState Rejected =
new WishListItemRejected();
internal static WishListItemState Accepted =
new WishListItemAccepted();
internal static WishListItemState InRealization =
new WishListItemInRealization();
internal static WishListItemState Realized =
new WishListItemRealized();
internal abstract WishListItemStatus Status { get; }
...
}
Step 2
Create subclass for every state.
Compile code and run tests.
Step 3
Move every method changing internal state to the State class.
Step 3
Move every method changing internal state to the State class.
public class WishListItem
{
...
public void AcceptBy(User user)
{
State.AcceptBy(user, this);
}
...
}
internal abstract class WishListItemState
{
...
internal void AcceptBy(User user, WishListItem item)
{
...
}
...
}
Step 3
Move every method changing internal state to the State class.
public class WishListItem
{
...
public void RejectBy(User user)
{
State.RejectBy(user, this);
}
...
}
internal abstract class WishListItemState
{
...
internal void RejectBy(User user, WishListItem item)
{
...
}
...
}
Step 3
Move every method changing internal state to the State class.
public class WishListItem
{
...
public void StartRealizationBy(User user)
{
State.StartRealizationBy(user, this);
}
...
}
internal abstract class WishListItemState
{
...
internal void StartRealizationBy(User user, WishListItem item)
{
...
}
...
}
Step 3
Move every method changing internal state to the State class.
public class WishListItem
{
...
public void FinishRealizationBy(User user)
{
State.FinishRealizationBy(user, this);
}
...
}
internal abstract class WishListItemState
{
...
internal void FinishRealizationBy(User user, WishListItem item)
{
...
}
...
}
Step 3
Move every method changing internal state to the State class.
Compile code and run tests.
Step 4
Copy logic of changing state to the concrete State class.
Step 4
Copy logic of changing state to the concrete State class.
public void AcceptBy(User user)
{
if (Status != WishListItemStatus.Requested &&
Status != WishListItemStatus.RequestedToDirector)
{
throw new CannotAcceptWishListItemWithCurrentStatusException(Status);
}
if (Status == WishListItemStatus.Requested)
{
if (!user.IsLeaderOf(_owner))
{
throw new UserDoesNotHavePermissionToAcceptRequestedWishListItemException();
}
if (ShouldBeRequestedToDirector())
{
Status = WishListItemStatus.RequestedToDirector;
}
else
{
Status = WishListItemStatus.Accepted;
}
return;
}
if (!user.IsDirectorOf(_owner))
{
throw new UserDoesNotHavePermissionToAcceptRequestedWishListItemException();
}
Status = WishListItemStatus.Accepted;
}
Step 4
Copy logic of changing state to the concrete State class.
internal class WishListItemRequested : WishListItemState
{
internal void AcceptBy(User user, WishListItem item)
{
if (!user.IsLeaderOf(item.Owner))
{
throw new UserDoesNotHavePermissionToAcceptRequestedWishListItemException();
}
if (ShouldBeRequestedToDirector(item.ItemCost))
{
item.State = RequestedToDirector;
}
else
{
item.State = Accepted;
}
}
internal void RejectBy(User user, WishListItem item)
{
if (!user.IsLeaderOf(item.Owner))
{
throw new UserDoesNotHavePermissionToRejectRequestedWishListItemException();
}
item.State = Rejected;
}
}
Step 4
Copy logic of changing state to the concrete State class.
internal class WishListItemRequestedToDirector : WishListItemState
{
internal void AcceptBy(User user, WishListItem item)
{
if (!user.IsDirectorOf(item.Owner))
{
throw new UserDoesNotHavePermissionToAcceptRequestedWishListItemException();
}
item.State = Accepted;
}
internal void RejectBy(User user, WishListItem item)
{
if (!user.IsDirectorOf(item.Owner))
{
throw new UserDoesNotHavePermissionToRejectRequestedWishListItemException();
}
item.State = Rejected;
}
}
Step 4
Copy logic of changing state to the concrete State class.
internal class WishListItemAccepted : WishListItemState
{
internal void StartRealizationBy(User user, WishListItem item)
{
if (!user.IsSupervisor)
{
throw new UserDoesNotHavePermissionToStartWishListItemRealizationException();
}
item.State = InRealization;
}
}
Step 4
Copy logic of changing state to the concrete State class.
internal class WishListItemInRealization : WishListItemState
{
internal void FinishRealizationBy(User user, WishListItem item)
{
if (!user.IsSupervisor)
{
throw new UserDoesNotHavePermissionToFinishWishListItemRealizationException();
}
if (!item.AreCostsInvoiced)
{
throw new CannotFinishWishListItemRealizationWithNotInvoicedException();
}
item.State = Realized;
}
}
Step 4
Copy logic of changing state to the concrete State class.
Compile code and run tests.
Step 5
Remove logic of changing state from the base State class.
Step 5
Remove logic of changing state from the base State class.
internal abstract class WishListItemState
{
...
internal abstract void AcceptBy(User user, WishListItem item);
internal abstract void RejectBy(User user, WishListItem item);
internal abstract void StartRealizationBy(User user, WishListItem item);
internal abstract void FinishRealizationBy(User user, WishListItem item);
...
}
Step 5
Remove logic of changing state from the base State class.
internal abstract class WishListItemState
{
...
internal virtual void AcceptBy(User user, WishListItem item)
{
throw new CannotAcceptWishListItemWithCurrentStatusException(Status);
}
internal virtual void RejectBy(User user, WishListItem item)
{
throw new CannotRejectWishListItemWithCurrentStatusException(Status);
}
internal virtual void StartRealizationBy(User user, WishListItem item)
{
throw new CannotStartWishListItemRealizationWithCurrentStatusException(Status);
}
internal virtual void FinishRealizationBy(User user, WishListItem item)
{
throw new CannotFinishWishListItemRealizationWithCurrentStatusException(Status);
}
...
}
Step 5
Remove logic of changing state from the base State class.
Compile code and run tests.
Summary
Pros and cons
Pros
- Reduces or eliminates conditional statements of changing state.
Pros
- Reduces or eliminates conditional statements of changing state.
- Simplifies complicated logic of changing state.
Pros
- Reduces or eliminates conditional statements of changing state.
- Simplifies complicated logic of changing state.
- Provides transparency in the logic of changing state.
Cons
- Complicates design when logic of changing state is not difficult to understand.
1. Introduction
2. Conditionals
3. Conditionals, part 2
4. Duplications
5. Summary
public class WishListItem
{
...
public decimal CalculateCost()
{
...
}
...
}
public decimal CalculateCost()
{
var totalCost = _baseItemCost;
if (_wishListItemType == WishListItemType.EducationMaterial)
{
if (_vendorsWithDiscounts.ContainsKey(_vendorName))
{
var discountAmount = totalCost * _vendorsWithDiscounts[_vendorName];
totalCost -= discountAmount;
}
}
...
}
public decimal CalculateCost()
{
...
if (_wishListItemType == WishListItemType.ELearningLicense)
{
var duration = _endDate - _startDate;
if (duration.HasValue && duration.Value.Days > 180)
{
totalCost *= 0.8m;
}
}
...
}
public decimal CalculateCost()
{
...
if (_wishListItemType == WishListItemType.Exam)
{
if (_approachNumber == 2)
{
totalCost /= 2;
}
else if (_approachNumber == 3)
{
totalCost /= 4;
}
else if (_approachNumber > 3)
{
totalCost = 0;
}
}
...
}
public decimal CalculateCost()
{
...
if (_wishListItemType == WishListItemType.Conference)
{
if (_location == LocationType.Foreign)
{
totalCost *= 0.7m;
}
}
...
}
public decimal CalculateCost()
{
...
if (_wishListItemType == WishListItemType.Conference
|| _wishListItemType == WishListItemType.Training
|| _wishListItemType == WishListItemType.Exam)
{
if (_sideCosts.IncludeAccommodationCost)
{
totalCost += _sideCosts.AccommodationCost;
}
if (_sideCosts.IncludeDailyAllowanceCost)
{
totalCost += _sideCosts.DailyAllowanceCost;
}
if (_sideCosts.IncludeTransportCost)
{
totalCost += _sideCosts.TransportCost;
}
}
return totalCost;
}
public decimal CalculateCost()
{
var totalCost = _itemCost;
if (_wishListItemType == WishListItemType.EducationMaterial)
{
if (_vendorsWithDiscounts.ContainsKey(_vendorName))
{
var discountAmount = totalCost * _vendorsWithDiscounts[_vendorName];
totalCost -= discountAmount;
}
}
if (_wishListItemType == WishListItemType.ELearningLicense)
{
var duration = _endDate - _startDate;
if (duration.HasValue && duration.Value.Days > 180)
{
totalCost *= 0.8m;
}
}
if (_wishListItemType == WishListItemType.Exam)
{
if (_approachNumber == 2)
{
totalCost /= 2;
}
else if (_approachNumber == 3)
{
totalCost /= 4;
}
else if (_approachNumber > 3)
{
totalCost = 0;
}
}
if (_wishListItemType == WishListItemType.Conference)
{
if (_location == LocationType.Foreign)
{
totalCost *= 0.7m;
}
}
if (_wishListItemType == WishListItemType.Conference
|| _wishListItemType == WishListItemType.Training
|| _wishListItemType == WishListItemType.Exam)
{
if (_sideCosts.IncludeAccommodationCost)
{
totalCost += _sideCosts.AccommodationCost;
}
if (_sideCosts.IncludeDailyAllowanceCost)
{
totalCost += _sideCosts.DailyAllowanceCost;
}
if (_sideCosts.IncludeTransportCost)
{
totalCost += _sideCosts.TransportCost;
}
}
return totalCost;
}
Strategy pattern
Create Strategy class.
Step 1
Create Strategy class.
public class WishListItemCostCalculationStrategy
{
}
Step 1
Step 2
Move considered method to the Strategy class.
Step 2
Move considered method to the Strategy class.
internal class WishListItemCostCalculationStrategy
{
internal decimal CalculateCost(WishListItem item)
{
var totalCost = item.BaseItemCost;
...
return totalCost;
}
}
Step 2
Move considered method to the Strategy class.
public class WishListItem
{
private readonly WishListItemCostCalculationStrategy _calculationStrategy;
...
public decimal CalculateCost()
{
var result = _calculationStrategy.CalculateCost(this);
return result;
}
}
Step 2
Move considered method to the Strategy class.
Compile code.
Step 3
Pass Strategy object as a parameter or create it inside the Context class.
Step 3
Pass Strategy object as a parameter or create it inside the Context class.
public class WishListItem
{
private readonly WishListItemCostCalculationStrategy _calculationStrategy;
public WishListItem(
...
WishListItemCostCalculationStrategy calculationStrategy)
{
...
_calculationStrategy = calculationStrategy;
}
...
}
Step 3
Pass Strategy object as a parameter or create it inside the Context class.
public class WishListItem
{
private readonly WishListItemCostCalculationStrategy _calculationStrategy;
public WishListItem(...)
{
...
_calculationStrategy = new WishListItemCostCalculationStrategy();
}
...
}
Step 3
Pass Strategy object as a parameter or create it inside the Context class.
Compile code and run tests.
Step 4
Replace conditional with polymorphism in the Strategy class.
Step 4
Replace conditional with polymorphism in the Strategy class.
internal decimal CalculateCost()
{
var totalCost = _itemCost;
if (_wishListItemType == WishListItemType.EducationMaterial)
{
...
}
if (_wishListItemType == WishListItemType.ELearningLicense)
{
...
}
if (_wishListItemType == WishListItemType.Exam)
{
...
}
if (_wishListItemType == WishListItemType.Conference)
{
...
}
...
}
Step 4
Replace conditional with polymorphism in the Strategy class.
internal class EducationMaterialCostCalculationStrategy : WishListItemCostCalculationStrategy
{
internal override decimal CalculateCost(WishListItem item)
{
var totalCost = item.BaseItemCost;
if (item.VendorsWithDiscounts.ContainsKey(item.VendorName))
{
var discountAmount = totalCost * item.VendorsWithDiscounts[item.VendorName];
totalCost -= discountAmount;
}
return totalCost;
}
}
Step 4
Replace conditional with polymorphism in the Strategy class.
internal class ELearningLicenseCostCalculationStrategy : WishListItemCostCalculationStrategy
{
internal override decimal CalculateCost(WishListItem item)
{
var totalCost = item.BaseItemCost;
var duration = item.EndDate - item.StartDate;
if (duration.HasValue && duration.Value.Days > 180)
{
totalCost *= 0.8m;
}
return totalCost;
}
}
Step 4
Replace conditional with polymorphism in the Strategy class.
internal class TrainingCostCalculationStrategy : WishListItemCostCalculationStrategy
{
internal override decimal CalculateCost(WishListItem item)
{
var totalCost = item.BaseItemCost;
if (item.SideCosts.IncludeAccommodationCost)
{
totalCost += item.SideCosts.AccommodationCost;
}
if (item.SideCosts.IncludeDailyAllowanceCost)
{
totalCost += item.SideCosts.DailyAllowanceCost;
}
if (item.SideCosts.IncludeTransportCost)
{
totalCost += item.SideCosts.TransportCost;
}
return totalCost;
}
}
Step 4
Replace conditional with polymorphism in the Strategy class.
internal class ConferenceCostCalculationStrategy : WishListItemCostCalculationStrategy
{
internal override decimal CalculateCost(WishListItem item)
{
var totalCost = item.BaseItemCost;
if (item.Location == LocationType.Foreign)
{
totalCost *= 0.7m;
}
if (item.SideCosts.IncludeAccommodationCost)
{
totalCost += item.SideCosts.AccommodationCost;
}
if (item.SideCosts.IncludeDailyAllowanceCost)
{
totalCost += item.SideCosts.DailyAllowanceCost;
}
if (item.SideCosts.IncludeTransportCost)
{
totalCost += item.SideCosts.TransportCost;
}
return totalCost;
}
}
Step 4
Replace conditional with polymorphism in the Strategy class.
internal class ExamCostCalculationStrategy : WishListItemCostCalculationStrategy
{
internal override decimal CalculateCost(WishListItem item)
{
var totalCost = item.BaseItemCost;
if (item.ApproachNumber == 2)
{
totalCost /= 2;
}
else if (item.ApproachNumber == 3)
{
totalCost /= 4;
}
else if (item.ApproachNumber > 3)
{
totalCost = 0;
}
...
}
}
Step 4
Replace conditional with polymorphism in the Strategy class.
internal class ExamCostCalculationStrategy : WishListItemCostCalculationStrategy
{
internal override decimal CalculateCost(WishListItem item)
{
...
if (item.SideCosts.IncludeAccommodationCost)
{
totalCost += item.SideCosts.AccommodationCost;
}
if (item.SideCosts.IncludeDailyAllowanceCost)
{
totalCost += item.SideCosts.DailyAllowanceCost;
}
if (item.SideCosts.IncludeTransportCost)
{
totalCost += item.SideCosts.TransportCost;
}
return totalCost;
}
}
Step 4
Replace conditional with polymorphism in the Strategy class.
internal abstract class WishListItemCostCalculationStrategy
{
internal abstract decimal CalculateCost(WishListItem item);
internal static WishListItemCostCalculationStrategy CreateStrategy(WishListItemType itemType)
{
switch (itemType)
{
case WishListItemType.ELearningLicense:
return new ELearningLicenseCostCalculationStrategy();
case WishListItemType.EducationMaterial:
return new EducationMaterialCostCalculationStrategy();
case WishListItemType.Exam:
return new ExamCostCalculationStrategy();
case WishListItemType.Conference:
return new ConferenceCostCalculationStrategy();
case WishListItemType.Training:
return new TrainingCostCalculationStrategy();
default:
throw new ArgumentOutOfRangeException(nameof(itemType), itemType,
"Unrecognized wish list item type.");
}
}
}
Step 4
Replace conditional with polymorphism in the Strategy class.
public class WishListItem
{
private readonly WishListItemCostCalculationStrategy _calculationStrategy;
public WishListItem(...)
{
...
_calculationStrategy = WishListItemCostCalculationStrategy.CreateStrategy(WishListItemType);
}
...
}
Step 4
Replace conditional with polymorphism in the Strategy class.
Compile code and run tests.
Summary
Pros and cons
Pros
- Provides algorithms clarity. Reduces or eliminates conditionals.
Pros
- Provides algorithms clarity. Reduces or eliminates conditionals.
- Simplifies context class. Moves variants of algorithm to the class hierarchy.
Pros
- Provides algorithms clarity. Reduces or eliminates conditionals.
- Simplifies context class. Moves variants of algorithm to the class hierarchy.
- Allows to change algorithm variant during program execution.
CONS
- Complicates design when algorithm is not complex.
CONS
- Complicates design when algorithm is not complex.
- Complicates getting data from context class.
1. Introduction
2. Conditionals
3. Conditionals, part 2
4. Duplications
5. Summary
internal override decimal CalculateCost(WishListItem item)
{
var totalCost = item.ItemCost;
...
if (item.SideCosts.IncludeAccommodationCost)
{
totalCost += item.SideCosts.AccommodationCost;
}
if (item.SideCosts.IncludeDailyAllowanceCost)
{
totalCost += item.SideCosts.DailyAllowanceCost;
}
if (item.SideCosts.IncludeTransportCost)
{
totalCost += item.SideCosts.TransportCost;
}
return totalCost;
}
Template method pattern
Step 1
Find similar method in multiple subclasses. Extract and unify identical and unique method(s).
Step 1
Find similar method in multiple subclasses. Extract and unify identical and unique method(s).
internal class EducationMaterialCostCalculationStrategy : WishListItemCostCalculationStrategy
{
internal override decimal CalculateCost(WishListItem item)
{
var totalCost = item.BaseItemCost;
if (item.VendorsWithDiscounts.ContainsKey(item.VendorName))
{
var discountAmount = totalCost * item.VendorsWithDiscounts[item.VendorName];
totalCost -= discountAmount;
}
return totalCost;
}
}
Step 1
Find similar method in multiple subclasses. Extract and unify identical and unique method(s).
internal class ELearningLicenseCostCalculationStrategy : WishListItemCostCalculationStrategy
{
internal override decimal CalculateCost(WishListItem item)
{
var totalCost = item.BaseItemCost;
var duration = item.EndDate - item.StartDate;
if (duration.HasValue && duration.Value.Days > 180)
{
totalCost *= 0.8m;
}
return totalCost;
}
}
Step 1
Find similar method in multiple subclasses. Extract and unify identical and unique method(s).
internal class EducationMaterialCostCalculationStrategy : WishListItemCostCalculationStrategy
{
internal override decimal CalculateCost(WishListItem item)
{
var totalCost = item.ItemCost;
totalCost = ModifyCostBySpecificRules(item, totalCost);
return totalCost;
}
private decimal ModifyCostBySpecificRules(WishListItem item, decimal totalCost)
{
if (item.VendorsWithDiscounts.ContainsKey(item.VendorName))
{
var discountAmount = totalCost * item.VendorsWithDiscounts[item.VendorName];
totalCost -= discountAmount;
}
return totalCost;
}
}
Step 1
Find similar method in multiple subclasses. Extract and unify identical and unique method(s).
internal class ELearningLicenseCostCalculationStrategy : WishListItemCostCalculationStrategy
{
internal override decimal CalculateCost(WishListItem item)
{
var totalCost = item.ItemCost;
totalCost = ModifyCostBySpecificRules(item, totalCost);
return totalCost;
}
private decimal ModifyCostBySpecificRules(WishListItem item, decimal totalCost)
{
var duration = item.EndDate - item.StartDate;
if (duration.HasValue && duration.Value.Days > 180)
{
totalCost *= 0.8m;
}
return totalCost;
}
}
Step 1
Find similar method in multiple subclasses. Extract and unify identical and unique method(s).
internal class TrainingCostCalculationStrategy : WishListItemCostCalculationStrategy
{
internal override decimal CalculateCost(WishListItem item)
{
var totalCost = item.BaseItemCost;
if (item.SideCosts.IncludeAccommodationCost)
{
totalCost += item.SideCosts.AccommodationCost;
}
if (item.SideCosts.IncludeDailyAllowanceCost)
{
totalCost += item.SideCosts.DailyAllowanceCost;
}
if (item.SideCosts.IncludeTransportCost)
{
totalCost += item.SideCosts.TransportCost;
}
return totalCost;
}
}
Step 1
Find similar method in multiple subclasses. Extract and unify identical and unique method(s).
internal class ExamCostCalculationStrategy : WishListItemCostCalculationStrategy
{
internal override decimal CalculateCost(WishListItem item)
{
var totalCost = item.BaseItemCost;
if (item.ApproachNumber == 2)
{
totalCost /= 2;
}
else if (item.ApproachNumber == 3)
{
totalCost /= 4;
}
else if (item.ApproachNumber > 3)
{
totalCost = 0;
}
...
}
}
Step 1
Find similar method in multiple subclasses. Extract and unify identical and unique method(s).
internal class ExamCostCalculationStrategy : WishListItemCostCalculationStrategy
{
internal override decimal CalculateCost(WishListItem item)
{
...
if (item.SideCosts.IncludeAccommodationCost)
{
totalCost += item.SideCosts.AccommodationCost;
}
if (item.SideCosts.IncludeDailyAllowanceCost)
{
totalCost += item.SideCosts.DailyAllowanceCost;
}
if (item.SideCosts.IncludeTransportCost)
{
totalCost += item.SideCosts.TransportCost;
}
return totalCost;
}
}
Step 1
Find similar method in multiple subclasses. Extract and unify identical and unique method(s).
internal class TrainingCostCalculationStrategy : WishListItemCostCalculationStrategy
{
internal override decimal CalculateCost(WishListItem item)
{
var totalCost = item.ItemCost;
totalCost += CalculateSideCosts(item.SideCosts);
return totalCost;
}
private decimal CalculateSideCosts(SideCosts sideCosts)
{
decimal totalSideCosts = 0;
if (sideCosts.IncludeAccommodationCost)
{
totalSideCosts += sideCosts.AccommodationCost;
}
if (sideCosts.IncludeDailyAllowanceCost)
{
totalSideCosts += sideCosts.DailyAllowanceCost;
}
if (sideCosts.IncludeTransportCost)
{
totalSideCosts += sideCosts.TransportCost;
}
return totalSideCosts;
}
}
Step 1
Find similar method in multiple subclasses. Extract and unify identical and unique method(s).
internal class ExamCostCalculationStrategy : WishListItemCostCalculationStrategy
{
internal override decimal CalculateCost(WishListItem item)
{
var totalCost = item.ItemCost;
totalCost = ModifyCostBySpecificRules(item, totalCost);
totalCost += CalculateSideCosts(item.SideCosts);
return totalCost;
}
private decimal ModifyCostBySpecificRules(WishListItem item, decimal totalCost)
{
if (item.ApproachNumber == 2)
{
totalCost /= 2;
}
else if (item.ApproachNumber == 3)
{
totalCost /= 4;
}
else if (item.ApproachNumber > 3)
{
totalCost = 0;
}
return totalCost;
}
...
}
Step 1
Find similar method in multiple subclasses. Extract and unify identical and unique method(s).
internal class ConferenceCostCalculationStrategy : WishListItemCostCalculationStrategy
{
internal override decimal CalculateCost(WishListItem item)
{
var totalCost = item.ItemCost;
totalCost = ModifyCostBySpecificRules(item, totalCost);
totalCost += CalculateSideCosts(item.SideCosts);
return totalCost;
}
private decimal ModifyCostBySpecificRules(WishListItem item, decimal totalCost)
{
if (item.Location == LocationType.Foreign)
{
totalCost *= 0.7m;
}
return totalCost;
}
...
}
Step 1
Find similar method in multiple subclasses. Extract and unify identical and unique method(s).
Compile code and run tests.
Step 2
Pull up identical method(s) to the base class.
Step 2
Pull up identical method(s) to the base class.
internal abstract class WishListItemCostCalculationStrategy
{
internal abstract decimal CalculateCost(WishListItem item);
protected decimal CalculateSideCosts(SideCosts sideCosts)
{
decimal totalSideCosts = 0;
if (sideCosts.IncludeAccommodationCost)
{
totalSideCosts += sideCosts.AccommodationCost;
}
if (sideCosts.IncludeDailyAllowanceCost)
{
totalSideCosts += sideCosts.DailyAllowanceCost;
}
if (sideCosts.IncludeTransportCost)
{
totalSideCosts += sideCosts.TransportCost;
}
return totalSideCosts;
}
...
}
Step 2
Pull up identical method(s) to the base class.
Compile code and run tests.
Step 3
Pull up similar method to the base class.
Step 3
Pull up similar method to the base class.
internal abstract class WishListItemCostCalculationStrategy
{
internal decimal CalculateCost(WishListItem item)
{
var totalCost = item.ItemCost;
totalCost = ModifyCostBySpecificRules(item, totalCost);
totalCost += CalculateSideCosts(item.SideCosts);
return totalCost;
}
protected abstract decimal ModifyCostBySpecificRules(WishListItem item, decimal totalCost);
protected virtual decimal CalculateSideCosts(SideCosts sideCosts)
{
decimal totalSideCosts = 0;
if (sideCosts.IncludeAccommodationCost)
{
totalSideCosts += sideCosts.AccommodationCost;
}
if (sideCosts.IncludeDailyAllowanceCost)
{
totalSideCosts += sideCosts.DailyAllowanceCost;
}
if (sideCosts.IncludeTransportCost)
{
totalSideCosts += sideCosts.TransportCost;
}
return totalSideCosts;
}
...
}
Step 3
Pull up similar method to the base class.
internal class ExamCostCalculationStrategy : WishListItemCostCalculationStrategy
{
protected override decimal ModifyCostBySpecificRules(WishListItem item, decimal totalCost)
{
if (item.ApproachNumber == 2)
{
totalCost /= 2;
}
else if (item.ApproachNumber == 3)
{
totalCost /= 4;
}
else if (item.ApproachNumber > 3)
{
totalCost = 0;
}
return totalCost;
}
}
Step 3
Compile code and run tests.
Pull up similar method to the base class.
Summary
Pros and cons
Pros
- Removes duplicates from subclasses. Moves the same operations and behaviors to the superclass.
Pros
- Removes duplicates from subclasses. Moves the same operations and behaviors to the superclass.
- Simplifies and clarifies steps of the general algorithm.
Pros
- Removes duplicates from subclasses. Moves the same operations and behaviors to the superclass.
- Simplifies and clarifies steps of the general algorithm.
- Allows to adjust algorithm by subclasses.
CONS
- Increases complexity when subclasses have to implement many methods.
Summary
Summary
Focus on the problem in your code.
Summary
Focus on the problem in your code.
Refactoring is better than overdesign.
Summary
Focus on the problem in your code.
Refactoring is better than overdesign.
Refactor in small steps. Don't forget about tests.
wojciech-dabrowski
wojciech-dabrowski
wojc.dabrowski@gmail.com
Q&A
Refactoring to design patterns
By Wojciech Dąbrowski
Refactoring to design patterns
Presentation about refactoring to design patterns.
- 140