7
\$\begingroup\$

Currently I'm developing my business entities. I feel that the structure is a tad cumbersome.

What I mean is that a store on a particular day can have different open and closing times. So it's not as simple as a store opens and closes at 09:00 and 17:00 respectively every day at the week. A store may open at 09:00 and 19:00 on only Thursdays etc.

Entities

public class Store
{
    public int StoreID { get; set;}
    public string StoreName { get; set; }
    public ICollection<DayOfWeek> DaysOpen { get; set; }
    public List<KeyValuePair<DayOfWeek, StoreTimes>> DaysAndTimes { get; set; }

}

public class StoreTimes
{
    public string StartTime { get; set;}
    public string EndTime { get; set; }
}

The initialisation is a but cumbersome, see below.

Initialisation

 Store store = new Store()
            {
                StoreID = 1,
                StoreName = "Some Store",
                DaysOpen = new List<DayOfWeek>() { DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday },
                DaysAndTimes = new List<KeyValuePair<DayOfWeek, StoreTimes>>()
                {
                    new KeyValuePair<DayOfWeek, StoreTimes>(DayOfWeek.Monday, new StoreTimes()
                    {
                        StartTime = "0900", EndTime = "1700"
                    }),
                    new KeyValuePair<DayOfWeek, StoreTimes>(DayOfWeek.Tuesday, new StoreTimes()
                    {
                        StartTime = "1000", EndTime = "1900"
                    })
                }

            };

Yes the initialisation needn't be a problem since the BLL will deal with it but how can this be refactored?

\$\endgroup\$
11
\$\begingroup\$

how can this be refactored?

Improved application of Single Responsibility Principle, encapsulation, and domain modeling.

Start With Good Data Structures

public class StoreTimes {
    public DayOfWeek Day {get; set;}
    public string StartTime { get; set; }
    public string EndTime { get; set; }
}
  • It is clear that an "hours of operation" must consist of a day along with open and close times.
  • So make a proper structure (class) that expresses this concept.
  • A complete data structure tends to be very flexible. In our case it will solve the problem @mjolka pointed out.
  • It simplifies the hell out of client code. i.e. dealing with "a list of key-value-pairs of days and store-times"

Ensure Objects Are Constructed Correctly

  • Constructor parameters forces the client (code) to supply the required things.
  • Arguments (the values passed via parameters) are easily validated up front before attempting to use bad or incomplete data.

.

public StoreTimes(DayOfWeek theDay, string startTime, string endTime)
    {
        Day = theDay;
        StartTime = startTime ?? "0000";
        EndTime = endTime ?? "0000";
    }

Don't allow Client to Arbitrarily Change State

  • non-public setters.

.

public class StoreTimes { 
    public DayOfWeek Day {get; protected set;}
    public string StartTime { get; protected set; }
    public string EndTime { get; protected set; }

Use Domain Language

Stores open and close, they do not start and stop.

public class StoreTimes {

    public DayOfWeek Day {get; protected set;}
    public string OpenTime { get; protected set; }
    public string CloseTime { get; protected set; }
}

Make Cohesive Classes

OO programming is an exercise in self-reliance, for the classes. A class should know how to do things that is should be doing itself. The client should only have to tell to do it.

  • At a restaurant you tell them to cook food, you don't do it for them.
  • You ask to see a menu, you don't write a menu for them.

.

public class StoreTimes {
    public string HoursOfOperation()
    {
        return String.Format("{0} : {1} to {2}", Day, OpenTime, CloseTime);
    }
}

New Class Applying All the Points Above

public class HoursOfOperation
{
    protected List<StoreTimes> TheHours { get; set; }

    public HoursOfOperation()
    {
        TheHours = new List<StoreTimes>();
    }

    public void Add(StoreTimes newOpsHours)
    {
        if (newOpsHours == null) return;

        TheHours.Add(newOpsHours);
    }

    public override string ToString()
    {
        StringBuilder me = new StringBuilder();

        foreach (StoreTimes openTime in TheHours)
            me.AppendLine(openTime.HoursOfOperation());

        return me.ToString();
    }
}

Lessons Learned Applied to Store Class

public class Store
{
    protected int StoreID { get; set; }
    protected string StoreName { get; set; }
    protected HoursOfOperation OperatingHours { get; set; }

    public Store(string storeName, HoursOfOperation operatingHours, int storeID ){
        StoreID = storeID;
        StoreName = storeName ?? "NoNameStore";

        if (operatingHours == null)
            throw new ArgumentNullException("Operating Hours is null");

        OperatingHours = operatingHours;
    }

    public string HoursOfOperation()
    {
        return this.OperatingHours.ToString();
    }

    public override string ToString()
    {
        StringBuilder me = new StringBuilder();
        me.AppendLine(string.Format("Store {0}  ID {1}", StoreName, StoreID ));
        me.AppendLine("Hours Of Operation:");
        me.AppendLine(HoursOfOperation());

        return me.ToString();
    }
}

Client Code

   static void Main(string[] args)
    {
        HoursOfOperation opsHours = new HoursOfOperation();
        opsHours.Add(new StoreTimes(DayOfWeek.Monday, "0900", "1130"));
        opsHours.Add(new StoreTimes(DayOfWeek.Monday, "1300", "1800"));
        opsHours.Add(new StoreTimes(DayOfWeek.Saturday, "0800", "2100"));
        opsHours.Add(new StoreTimes(DayOfWeek.Sunday, null, null));

        Store CostingTons = new Store("CostingTons", opsHours, 1);
        Console.WriteLine(CostingTons.ToString());
        Console.ReadLine();
  }
\$\endgroup\$
  • \$\begingroup\$ thank you so much for this. Your points make sense. This seems like it will allow for for varying operation hours from week to week. So if a store is closing early for a week, yes I'll modify the structure to support this, but this will get me onto the right track. \$\endgroup\$ – ZeroBased_IX May 24 '15 at 7:43
  • \$\begingroup\$ Me: "Hey! Look at that great answer! Oh... It's @radarbob... of course it's a great answer." \$\endgroup\$ – RubberDuck May 24 '15 at 11:16
2
\$\begingroup\$

You're interested in a Dictionary<DayOfWeek, StoreTimes>() which, coincidentally, actually inherits from IEnumerable<KeyValuePair<TKey, TValue>>.

Additionally I would also suggest adding a StoreTimes(string start, string end) constructor so you can initialize properly (and it should be a requirement either way).

This would result in code like this:

DaysAndTimes = new Dictionary<DayOfWeek, StoreTimes>()
{
    { DayOfWeek.Monday, new StoreTimes("1300", "1700") },
    { DayOfWeek.Tuesday, new StoreTimes("0900", "1700") }
}

Additionally, I would suggest turning your DaysOpen into a HashSet or an array so you don't have to deal with an O(n) lookup. Or you omit it altogether and just combine the usage with your dictionary: only add the days you're open in DaysAndTimes and try to retrieve the day from there.

\$\endgroup\$
  • 2
    \$\begingroup\$ A Dictionary<DayOfWeek, StoreTimes> might be too limiting, as it won't allow for opening hours such as Monday 9:30 am – 2:00 pm, 4:00 – 8:00 pm. Opening hours like these are very common in Spain, at least. \$\endgroup\$ – mjolka May 24 '15 at 0:07
  • \$\begingroup\$ @mjolka: that's something that should be solved in StoreTimes though, I would still stay with the dictionary to relate a day to (a certain set of) openinghours. \$\endgroup\$ – Jeroen Vannevel May 24 '15 at 0:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.