views:

218

answers:

2

How should I refactor this design?

class Service : IChargeable, IExtendable<br />
{

}

interface IChargeable
{
decimal? ChargeableAmount { 
    get; 
    }
}

interface IExtendable
{
bool IsExtendable { 
    get; 
    }
}

class DayService : Service { } 
class NightService : Service { } 
class TwentFourService : Service { }

The problem is that these services are charged at varied rates, depending on whether they are extendable or depending on their type.
A percentage of the chargeable amount is charged if the service is extended. The service charge is ChargeableAmount * Seasonal rate, which differes for each service.

I do not want to store the rates in the Service Class or any of the derived classes. Would it be a good idea to create a SeasonalRate provider which has methods for each ServiceType which return the SeasonalRate?
The problem is that the seasonal rates can change and do change often and we are wary of making changes to the Service Classes. We would also like to have maintainable dependencies on the Service Classes in our SeasonalRates classes.
Is it a good idea to implement a SeasonalRates class with overloads for each ServiceClass (a sort of a visitor)? Would it be a problem that all SeasonalRates classes will have to implement methods for all ServiceTypes?
What interface should the SeasonalRate class implement as there will be other classes too which calculate rates such as DiscountRate, ZipCodeRate, etc.?
If more ServiceType classes are added, will it be difficult to maintain changes to all Visitors?

Edit: All rates vary by ServiceType. For example, SeasonalRates for Day and Night Services are different

What would be advantages/disadvantages of the approach suggested by Jeff Meatball Yang over using the standard visitor pattern like follows:

interface IVisitable
{
    Accept(IVisitor visitor);
}
interface IVisitor
{
    Visit(IVisitable visitable);
}
interface IRate
{
    Rate { get; }
}

class Service : IVisitable
{
    public virtual Accept(IVisitor visitor);
}
class Visitor : IVisitor
{
    public virtual Visit(IVisitable visitable) { }
    public virtual Visit(DayService visitable) { }
    public virtual Visit(NightService visitable) { }
}
class RateVisitor : Visitor, IRateVisitor
{
    decimal? _Rate;

    override Visit(IVisitable visitable) { };
    override Visit(DayService visitable) { // Logic Goes here, sets the _Rate Variable };
    // Overrides for other services

    public virtual Rate 
    {
        return this._Rate;
    }
}
+2  A: 

Hello!

It seems that you are looking for the Strategy pattern: several way to calculate something? just use a common interface, and implement different behaviors in concrete objects.

What about:

class Service : IChargeable, IExtendable
{

}

interface IChargeable {  
    decimal? ChargeableAmount { get; }  
}

interface IExtendable {  
    bool IsExtendable { get; }  
}

class RatedService: Service {  
    RateStrategy strategy;  
}

interface RateStrategy {  
    decimal SeasonalRate { get; }  
}

class DiscountRate: RateStrategy {}  
class ZipCodeRate: RateStrategy {}  

class DayService : RatedService { }  
class NightService : RatedService { }  
class TwentFourService : RatedService { }
NicDumZ
Hi. Thx for the ans. The issue is that not all RateStrategies are the same. ZipCodeRate for example will never return a null. Similarly, there are other operations also to be added, for example get product thumbnail, etc. That would require a lot of different Strategy base types and frequent changes to the Service objects.
SharePoint Newbie
You mean changes to the objects inherited from RatedService?Well each time you want to change the Strategy used by a type of service, you'll have to change a few lines in the inherited class. Aka if you discount DayService, you'll have to change something in that Class.What I'm proposing here, is to change: DayService() { super(new DefaultStrategy(); }to: DayService() { super(new Discount(0.8)); }It looks like a very small amount of changes...
NicDumZ
+1: I do something like this in our app, but for tax calculations: I have a dif strategy method depending on the province that the application is functioning in.
SnOrfus
A: 

This is an interesting problem. I'd like to reflect on the goal of this design, so you can correct me if I am mistaken.

From your description, it seems like a Service can have multiple Rates. A Service instance relates to one (or more?) of the following instances:

SeasonalRate, DiscountRate, ZipCodeRate, etc.

I also gather that there is such a thing as a Visitor object, and it relates to one or more Services:

DayService, NightService, TwentyFourService, etc.

I believe the task here is to correctly charge a Visitor, depending on the properties of the Service being performed, and the Rates that are applicable?

Visualizing the relationships should help you understand the interfaces that are necessary. It seems that all Rates must implement an interface that all Services can call.

public interface IRate { 
  decimal GetRate(Service s);
}

This allows Rates to decide, based on the Service, what the return value should be. Now it would be important that all Services implement the interfaces (IChargeable, IExtendable, etc.) so that all the data necessary to make a decision is available to every Rate object.

Here's an example of what I am thinking:

public interface IThumbnail {
  string ThumbnailFile { get; }
  int ThumbnailSize { get; }
  Image GetThumbnail();
}
public interface IThumbnailProvider { 
  Image GetThumbnail(Service s);
}

public class Thumbnail : IThumbnailProvider { 
    public Image GetThumbnail(Service s) { 
       if(s.ThumbnailFile != null) {
           // get the image
       }
    }
}

public interface ISeasonal {
  SeasonType Season { get; }
}

public class SeasonalRate : IRate {
  public decimal GetRate(Service s) { 
    if(s.Season != SeasonType.None) {
       // return seasonal rate based on s.Season
    } else {
       // throw an exception or do something else
    }
}

public abstract class Service : IChargeable, IExtendable, ISeasonal, IThumbnail { 
  // default implementation for ISeasonal
  protected SeasonType _season = SeasonType.None;
  public SeasonType Season { get { return _season; } }


  // default implementation for IThumbnail
  protected string _thumbFile = null;
  protected int _thumbSize = 32;
  public string ThumbnailFile { get { return _thumbFile; } }
  public int ThumbnailSize { get { return _thumbnailSize; } }
  public Image GetThumbnail() {
     return null; // 
  } 
}

public class DayService : Service { 
  private IRate _confirmedRate;
  private IThumbnailProvider _fileServer;
  public decimal GetRate() { return confirmedRate.GetRate(this); }
  public Image GetThumbnail() { return fileServer.GetThumbnail(); }

  // constructor
  public DayService(IThumbnailProvider fileServer, IRate confirmedRate) { 
        this._season = SeasonType.Day; 
        this._filename = "sunny.jpg"; 
        this._fileServer = fileServer;
        this._confirmedRate = confirmedRate;
  }
}

This lets your Rate objects change according to your needs, but reduces the changes necessary to each Service, since your business variables should be pretty well defined and relatively unchanging in the Service base class.

For example, your NightService may not have a thumbnail, and so it would not need to implement the GetThumbnail function explicitly. It simply inherits the default implementation on Service.

Jeff Meatball Yang
Hi, Rate is just an example. There are also other properties such as Service Description, Thumbnail, etc. If nw properties are introduced, the Service class will have to change. Thats why we initially went with the Visitor.
SharePoint Newbie
Also, Seasonal Rates vary by the service.
SharePoint Newbie
There will be no harm to add and implement new properties (interfaces) on the Service class, since it is your base (abstract) class that all other Services derive from. Each property will implement an interface that takes the Service as a parameter. See my edits.
Jeff Meatball Yang
Could you compare your design with what we had initially thought of. Posted that as edit to question.
SharePoint Newbie
Posted your solution as part of a followup question at http://stackoverflow.com/questions/931790/which-of-the-following-two-designs-is-the-way-to-go
SharePoint Newbie
You know what, I didn't know what the Visitor pattern was, but I think that I am convinced that the Visitor is the right way to go. I might have been trying to get there myself. So thanks foe the thought exercise. :)
Jeff Meatball Yang