简体   繁体   中英

Business Object is duplicating some properties for Presentation Layer

I have a Business Object (Domain Object) representing an employee's shift timings. Its name is EmployeeWorkShift .

using System;

namespace BusinessObjects
{
  public class EmployeeWorkShift
  {
    public long EmployeeWorkShiftId { get; set; }
    public long EmployeeId { get; set; }
    public DateTime StartDate { get; set; }
    public DateTime EndDate { get; set; }
    public TimeSpan StartTime { get; set; }
    public TimeSpan EndTime { get; set; }
  }
}

I have a Repository to create, read, update and delete this Business Object in database. Its name is IEmployeeWorkShiftRepository .

I have a Service which has methods to perform operations with this Business Object. Its name is IEmployeeWorkShiftService .

The User Interface call the Service methods for different events:

  1. To retrieve all EmployeeWorkShift objects of an employee, it calls List<EmployeeWorkShift> GetEmployeeWorkShifts(long employeeId); method

  2. To retrieve a specific EmployeeWorkShift object, it calls EmployeeWorkShift GetEmployeeWorkShift(long employeeWorkShiftId); method

  3. To insert a specific EmployeeWorkShift object, it calls EmployeeWorkShift InsertEmployeeWorkShift(EmployeeWorkShift employeeWorkShift); method

  4. To update a specific EmployeeWorkShift object, it calls EmployeeWorkShift UpdateEmployeeWorkShift(EmployeeWorkShift employeeWorkShift); method

  5. To delete a specific EmployeeWorkShift object, it calls void DeleteEmployeeWorkShift(EmployeeWorkShift employeeWorkShift); method

Now in the User Interface, for retrieve/insert/update, the user wants to use some specific formats for dates and times of EmployeeWorkShift object.

One way to solve this issues, is to add 4 string properties in EmployeeWorkShift object which contains the dates and times in specific formats user desires:

using System;

namespace BusinessObjects
{
  public class EmployeeWorkShift
  {
    public long EmployeeWorkShiftId { get; set; }
    public long EmployeeId { get; set; }
    public DateTime StartDate { get; set; }
    public DateTime EndDate { get; set; }
    public TimeSpan StartTime { get; set; }
    public TimeSpan EndTime { get; set; }
    public string StartDateString { get; set; }
    public string EndDateString { get; set; }
    public string StartTimeString { get; set; }
    public string EndTimeString { get; set; }
  }
}

So in User Interface I don't use the original 4 properties of dates and times and instead use the new 4 string properties.

In Service method for retrieve, once I get data from Repository, I translate the original 4 properties of dates and times retrieved from database into specific formats and populate the new 4 string properties.

In Service method for insert/update, I translate the new 4 string properties into original 4 properties of dates and times before calling Repository.

This looks a crude solution to me. Is there a better way to solve this issue?

In my view, formatting Dates for display purposes is a presentation concern, not a business logic concern. I would imagine also that the formatting for dates affects all dates that the user sees, not only the dates related EmployeeWorkShift, so your approach would require extending every single entity which contains dates with the string property and applying the logic everywhere.

What I would do is have the business objects working with DateTime only both for reads and writes. Then in the presentation layer I would have a DateTime formatter which would accept the DateTime and a Format parameter. The Format parameter could be retrieved from User settings or obtained from the selected Culture for example.

So, you'd have the concerns separated into 3 parts:

  1. Business logic works with DateTimes always. This will simplify the business logic layer and avoid mistakes

  2. A single Formatter function to format any DateTime for display regardless of the business object it belongs to (you'll need a Parser function too).

  3. A single way to retrieve the format, decoupled from all business objects and dates presented in the UI, so you can easily replace how you obtain it (combo box on the page, from the Culture in the browser or system, from user settings, etc).

It is possible to create one field which will store format of your DateTime or TimeSpan . By using this property you can format your value for presentation layer. Let's call this property as DateFormat .

Then at runtime you can decide what formatter should be used to format your value. It can be done through Strategy pattern . Strategies of formatting will be stored in collection and you can get instance of strategy by using Factory pattern .

So let's see an example of implementation. This is property DateFormat :

public enum DateFormat
{ 
    Date, Time
} 

And your class EmployeeWorkShift :

public class EmployeeWorkShift
{
    // the other code is omitted for the brevity

    public DateFormat DateFormat { get; set; }

    public string StartDate { get; set; }

    public string EndDate { get; set; }

    // the other code is omitted for the brevity
}

And then you can create abstract class DateFormatter which will define behaviour for all derived classes. These derived classes will format values:

public abstract class DateFormatter 
{
    public abstract string Format(string dateTime);
}

public class DateTimeFormatter : DateFormatter
{
    public override string Format(string dateTime) => 
        DateTime.Parse(dateTime).ToString();
}

public class TimeSpanFormatter : DateFormatter
{
    public override string Format(string dateTime) =>
        TimeSpan.Parse(dateTime).ToString();
}

And we need a factory which will return instance of formatter by DateFormat :

public class DateFormatFactory 
{
    private Dictionary<DateFormat, DateFormatter> _formattersByDateFormat = 
        new Dictionary<DateFormat, DateFormatter>()
    {
        { DateFormat.Date, new DateTimeFormatter() },
        { DateFormat.Time, new TimeSpanFormatter() }
    };

    public DateFormatter GetInstance(DateFormat dateFormat) => 
        _formattersByDateFormat[dateFormat];
}

And this is a mock method of getting shifts:

IEnumerable<EmployeeWorkShift> GetShifts() => new List<EmployeeWorkShift>
{
    new EmployeeWorkShift { DateFormat = DateFormat.Date, StartDate="2022-12-25" },
    new EmployeeWorkShift { DateFormat = DateFormat.Time, StartDate="6:12:14:45" }
};

And another method to show formatted values:

void ShowFormattedValues() 
{
    DateFormatter dateFormatter;
    DateFormatFactory dateFormatFactory = new DateFormatFactory();
    foreach (EmployeeWorkShift shift in GetShifts())
    {
        dateFormatter = dateFormatFactory.GetInstance(shift.DateFormat);
        Console.WriteLine("formatted value: " 
            + dateFormatter.Format(shift.StartDate));
    }
}

And you can call it like this:

ShowFormattedValues();

So we are getting strategy to format values based on DateFormat property. And the pattern name is Strategy .

So our code follows Open Closed principle of SOLID principles . When you will want to add new strategy of formatting you will need a new class of FooFormatter .

In addition, there is no need to write twice name in method and service. You can have:

public interface IEmployeeWorkShiftRepository
{
    string GetById();

    string GetByShiftId();

    void Insert();

    void Update();

    void Delete();
}

and call it like this:

employeeWorkShiftRepository.GetById(); // 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM