Вход на дизайн класса - PullRequest
       11

Вход на дизайн класса

2 голосов
/ 22 октября 2009

В настоящее время у меня есть классы обслуживания, которые выглядят примерно так

public class UserService : IUserService 
{
    private IAssignmentService _assignmentService;
    private ILocationService _locationService;
    private IUserDal _userDal;
    private IValidationDictionary _validationDictionary;
    public UserService(IAssignmentService assignmentService, ILocationService locationService, IValidationDictionary validationDictionary)
    {
        this._assignmentService = assignmentService;
        this._locationService = locationService;
        this._userDAL = new UserDal();
        this._validationDictionary = validationDictionary;
    }

    .....

    private void ValidateUser(IUser user)
    {
       if (_locationService.GetBy(user.Location.Id) == null)
          _validationDictionary.AddError("....");
       if (_assignmentService.GetBy(user.Assignment.Id) == null)
          _validationDictionary.AddError("....");
       .....
    }
}

И классы DAL, которые выглядят так:

public class UserDal: IUserDal
{
    private IAssignmentDal _assignmentDal;
    private ILocationDAL _locationDAL

    public UserDal()
    {
        this_assignmentDal = new AssignmentDal();
        this._locationDal = new LocationDal();
    }

    public int AddUser(IUser user)
    {
       // db call and insert user
       _locationDal.Add(user.Location);
       _assignmentDal.Add(user.Assignment);
    }

    public IUser GetUser(int id)
    {
       ..DB Call

       IUser user = new User() { userData, GetLocation(dr["Location_Id"]),GetAssignment([dr["Assignment_Id"]);
       return user
    }

    private ILocation GetLocation(int id)
    {
        return new LocationDal().GetById(id);
    }
    private IAssignment GetAssignment(int id)
    {
        return new AssignmentDal().GetById(id);
    }
}

Мне было интересно, считалось ли плохим замыслом говорить, чтобы сервисный уровень взаимодействовал с другими объектами сервисного уровня, а Dal взаимодействовал с другими объектами Dal?

Заранее спасибо

Ответы [ 3 ]

5 голосов
/ 22 октября 2009

Учитывая дизайн ваших примеров, вы столкнетесь с тем, что я люблю называть адом зависимости. Это, безусловно, вариант пойти по выбранному вами маршруту, но это приведет к высокосвязанной архитектуре, которую, вероятно, будет очень сложно поддерживать и реорганизовать. Однако, если вы абстрагируетесь немного больше, вы можете упростить свою архитектуру, немного упорядочить обязанности и разделить задачи таким образом, чтобы управление вашими зависимостями стало намного проще.

UserService, AssignmentService и LocationService выглядят как службы в стиле CRUD. Более подходящим термином для них будет Entity Services. Служба сущности должна нести единоличную ответственность за операции CRUD непосредственной сущности и ничего больше. Операции, которые включают в себя несколько сущностей, взаимосвязи сущностей и т. Д., Могут быть переданы в сервис более высокого уровня, который может управлять крупномасштабными операциями. Их часто называют Orchestration или Task Services.

Я бы рекомендовал подход, подобный следующему. Цели здесь состоят в том, чтобы упростить каждый сервис, чтобы дать ему минимальный объем ответственности, и контролировать зависимости. Упростите свои сервисные контракты, чтобы уменьшить ответственность существующих сервисов организации, и добавьте две новые услуги:

// User Management Orchestration Service
interface IUserManagementService
{
    User CreateUser();
}

// User Entity Service
interface IUserService
{
    User GetByKey(int key);
    User Insert(User user);
    User Update(User user);
    void Delete(User user);
}

// User Association Service
interface IUserAssociationService
{
    Association FindByUser(User user);
    Location FindByUser(User user);
    void AssociateWithLocation(User user, Location location);
    void AssociateWithAssignment(User user, Assignment assignment);
}

// Assignment Entity Service
interface IAssignmentService
{
    Assignment GetByKey(int key);
    // ... other CRUD operations ...
}

// Location Entity Service
interface ILocationService
{
    Location GetByKey(int key);
    // ... other CRUD operations ...
}

Процесс создания пользователя и связывания его с местоположением и назначением будет принадлежать UserManagementService, который будет составлять сервисы объектов более низкого уровня:

class UserManagementService: IUserManagementService
{
    public UserManagementService(IUserService userService, IUserAssociationService userAssociationService, IAssignmentService assignmentService, ILocationService locationService)
    {
        m_userService = userService;
        m_userAssociationService = userAssociationService;
        m_assignmentService = assignmentService;
        m_locationService = locationService;
    }

    IUserService m_userService;
    IUserAssociationService m_userAssociationService;
    IAssignmentService m_assignmentService;
    ILocationService m_locationService;

    User CreateUser(string name, {other user data}, assignmentID, {assignment data}, locationID, {location data})
    {
        User user = null;
        using (TransactionScope transaction = new TransactionScope())
        {
            var assignment = m_assignmentService.GetByKey(assignmentID);
            if (assignment == null)
            {
                assignment = new Assignment { // ... };
                assignment = m_assignmentService.Insert(assignment);
            }

            var location = m_locationService.GetByKey(locationID);
            if (location == null)
            {
                location = new Location { // ... };
                location = m_locationService.Insert(location);
            }

            user = new User
            {
                Name = name,
                // ...
            };
            user = m_userService.Insert(user);
            m_userAssociationService.AssociateWithAssignment(user, assignment);
            m_userAssociationService.AssociateWithLocation(user, location);
        }

        return user;
    }
}

class UserService: IUserService
{
    public UserService(IUserDal userDal)
    {
        m_userDal = userDal;
    }

    IUserDal m_userDal;

    public User GetByKey(int id)
    {
        if (id < 1) throw new ArgumentException("The User ID is invalid.");

        User user = null;
        using (var reader = m_userDal.GetByID(id))
        {
            if (reader.Read())
            {
                user = new User
                {
                    UserID = reader.GetInt32(reader.GerOrdinal("id")),
                    Name = reader.GetString(reader.GetOrdinal("name")),
                    // ...
                }
            }
        }

        return user;
    }

    public User Insert(User user)
    {
        if (user == null) throw new ArgumentNullException("user");
        user.ID = m_userDal.AddUser(user);
        return user;
    }

    public User Update(User user)
    {
        if (user == null) throw new ArgumentNullException("user");
        m_userDal.Update(user);
        return user;
    }

    public void Delete(User user)
    {
        if (user == null) throw new ArgumentNullException("user");
        m_userDal.Delete(user);
    }
}

class UserAssociationService: IUserAssociationService
{
    public UserAssociationService(IUserDal userDal, IAssignmentDal assignmentDal, ILocationDal locationDal)
    {
        m_userDal = userDal;
        m_assignmentDal = assignmentDal;
        m_locationDal = locationDal;
    }

    IUserDal m_userDal;
    IAssignmentDal m_assignmentDal;
    ILocationDal m_locationDal;

    public Association FindByUser(User user)
    {
        if (user == null) throw new ArgumentNullException("user");
        if (user.ID < 1) throw new ArgumentException("The user ID is invalid.");

        Assignment assignment = null;
        using (var reader = m_assignmentDal.GetByUserID(user.ID))
        {
            if (reader.Read())
            {
                assignment = new Assignment
                {
                    ID = reader.GetInt32(reader.GetOrdinal("AssignmentID")),
                    // ...
                };

                return assignment;
            }
        }
    }
}

class UserDal: IUserDal
{
    public UserDal(DbConnection connection)
    {
        m_connection = connection;
    }

    DbConnection m_connection;

    public User GetByKey(int id)
    {
        using (DbCommand command = connection.CreateCommand())
        {
            command.CommandText = "SELECT * FROM Users WHERE UserID = @UserID";
            var param = command.Parameters.Add("@UserID", DbType.Int32);
            param.Value = id;

            var reader = command.ExecuteReader(CommandBehavior.SingleResult|CommandBehavior.SingleRow|CommandBehavior.CloseConnection);
            return reader;                
        }
    }

    // ...
}

class AssignmentDal: IAssignmentDal
{

    public AssignmentDal(DbConnection connection)
    {
        m_connection = connection;
    }

    DbConnection m_connection;

    Assignment GetByUserID(int userID)
    {
        using (DbCommand command = connection.CreateCommand())
        {
            command.CommandText = "SELECT a.* FROM Assignments a JOIN Users u ON a.AssignmentID = u.AssignmentID WHERE u.UserID = @UserID";
            var param = command.Parameters.Add("@UserID", DbType.Int32);
            param.Value = id;

            var reader = command.ExecuteReader(CommandBehavior.SingleResult|CommandBehavior.SingleRow|CommandBehavior.CloseConnection);
            return reader;                
        }
    }

    // ...
}

// Implement other CRUD services similarly

Концептуальные уровни и потоки данных / объектов, которые возникают в результате этой архитектуры, будут следующими:

Task:                         UserManagementSvc
                                      ^
                                      |
             -------------------------------------------------
             |              |                 |              |
Entity:  UserSvc   UserAssociationsSvc   AssignmentSvc   LocationSvc
             ^       ^                        ^              ^
             |       |                        |              |
             ---------                        -              -
                 |                            |              |
Utility:      UserDal                   AssignmentDal   LocationDal
                 ^                            ^              ^
                 |                            |              |
                 ---------------------------------------------
                                       |
DB:                             (SQL Database)

Здесь следует отметить пару ключевых моментов, касающихся состава и зависимостей. Добавляя UserManagementService и создавая в нем службы сущностей, вы достигаете следующего:

  1. Устранение связи между службами сущностей.
  2. Сокращение объема зависимостей для каждой сущности сервиса.
    • Они зависят только от своего DAL и, возможно, общей инфраструктуры.
  3. Зависимости теперь однонаправлены: все зависимости «нисходящие», никогда «горизонтальные» или «восходящие».
    • Это простое правило обеспечивает очень простой механизм, с помощью которого можно полностью устранить недисциплинированные зависимости.
  4. Правила связывания пользователя с назначением и местоположением удаляются из сущностей и поднимаются выше.
    • Это обеспечивает более гибкие композиции и стимулирует повторное использование кода.
    • Могут быть написаны другие службы, такие как UserManagementService, которые по-разному объединяют пользователя, назначение и местоположение и / или другие объекты для удовлетворения различных бизнес-правил и решения различных проблем.
  5. Даже сервисы более высокого уровня могут быть написаны выше UserManagementService и аналогичных сервисов, составляя их аналогичным образом, создавая рабочие процессы даже более высокого уровня с минимальными усилиями.

Если вы осторожны в том, как вы проектируете и пишете каждый уровень услуг, вы можете предоставить много уровней разной ответственности, сложности и компоновки. Приложение становится меньше о написании бизнес-правил и больше о создании частей для создания бизнес-поведения. Если часть должна быть написана, она обычно может быть написана путем составления других частей и, возможно, добавления небольшого количества дополнительного поведения. Построение приложения становится намного проще, и гораздо проще создать полностью функциональные, автономные, повторно используемые детали, которые легче тестировать изолированно и проще в развертывании.

Вы также достигнете Сервис-Ориентации в прямом смысле (в любом случае, согласно Томасу Эрлу ) вместе со всеми его преимуществами. ;)

0 голосов
/ 22 октября 2009

Вы можете следовать модели, аналогичной модели «DataContext», за которой следует LINQ2SQL или Entityframework, т. Е. Иметь Контекст, который отслеживает такие объекты, как «Пользователь» и т. Д. Ваш уровень обслуживания, в свою очередь, просто общается с Контекстом, чтобы выполнить запрос сущности или выполнять операции между сущностями, если это необходимо. Таким образом, вашим индивидуальным объектам, таким как объект «Пользователь», не нужно будет иметь непосредственное знание (связывание) с другими «несвязанными» объектами, и Контекст предоставляет эти объекты клиенту.

0 голосов
/ 22 октября 2009

Я не знаю, что вы подразумеваете под Dal, но обычно вам необходимо сбалансировать уровень сцепления и сцепления в вашем приложении.

В этом случае я бы задал вопрос, почему экземпляр UserService может проверять другие IUsers. Это похоже на связь, которая может быть проблематичной. Тем не менее, я не знаю ваше приложение, поэтому я могу ошибаться.

Нарисуйте диаграмму классов, чтобы было легко увидеть связь между классами.

...