【问题标题】:Can domain model/class return self?域模型/类可以返回自我吗?
【发布时间】:2015-10-21 03:25:18
【问题描述】:

在 MVC 中,在域模型中移动逻辑是否有意义?我试图减少太多的类,因为我的项目使用多个 API 来处理要在页面上显示的每个小数据。 例如

public class AccountModel{
    public int Id {get;set;}    //property  
    ....    

    public List<AccountModel> GetAccounts(){    //method  
        ....      
    }  
}

否则,如果涉及太多 API 调用,有什么好的做法?


添加类似于我在项目中所做的示例代码结构。

    public class TestController : Controller
    {
        public ActionResult Index(string id)
        {
            var testService = new TestService();
            var testModel = new TestModel();

            testModel.UserData = testService.GetTestData(id);

            testModel.MenuList = testService.GetMenu(id);

            testModel.UserItems = testService.GetItems(id);

            return View(testModel);
        }   
    }
    -----------------------------------------------------------------
    public class TestService
    {
        public TestModel GetTestData(string id)
        {
            TestModel testData = null;
            try
            {
                using (HttpClient httpClient = new HttpClient())
                {
                    string requestUri = "http://10.8.200.1/test/" + id;

                    HttpRequestMessage testRequest = new HttpRequestMessage(HttpMethod.Get, requestUri);

                    HttpResponseMessage response = httpClient.SendAsync(testRequest).Result;
                    if (response.IsSuccessStatusCode)
                    {
                        var jsonData = response.Content.ReadAsStringAsync().Result;
                        testData = JsonConvert.DeserializeObject<TestModel>(jsonData);
                    }
                }
            }
            catch (Exception ex)
            {           
            }

            return testData;
        }

        public List<Menu> GetMenu(string id)
        {
            List<Menu> menus = null;
            try
            {
                using (HttpClient httpClient = new HttpClient())
                {
                    string requestUri = "http://10.8.200.1/menu/" + id;

                    HttpRequestMessage testRequest = new HttpRequestMessage(HttpMethod.Get, requestUri);

                    HttpResponseMessage response = httpClient.SendAsync(testRequest).Result;
                    if (response.IsSuccessStatusCode)
                    {
                        var jsonData = response.Content.ReadAsStringAsync().Result;
                        menus = JsonConvert.DeserializeObject<List<Menu>>(jsonData);
                    }
                }
            }
            catch (Exception ex)
            {           
            }

            return menus;
        }

        public List<UserItem> GetItems(string id)
        {
            List<UserItem> items = null;
            try
            {
                using (HttpClient httpClient = new HttpClient())
                {
                    string requestUri = "http://10.8.200.1/items/" + id;

                    HttpRequestMessage testRequest = new HttpRequestMessage(HttpMethod.Get, requestUri);

                    HttpResponseMessage response = httpClient.SendAsync(testRequest).Result;
                    if (response.IsSuccessStatusCode)
                    {
                        var jsonData = response.Content.ReadAsStringAsync().Result;
                        items = JsonConvert.DeserializeObject<List<Menu>>(jsonData);
                    }
                }
            }
            catch (Exception ex)
            {           
            }

            return items;
        }
    }
    -----------------------------------------------------------------
    public class TestModel
    {
        public string Name{get;set;}
        public string PublisherId{get;set;}
        public string AccountType{get;set;}

        public UserData UserData {get;set;}         //There will be a UserData model
        public List<Menu> MenuList {get;set;}       //There will be a Menu model
        public List<UserItem> UserItems {get;set;}  //There will be a UserItem model
    }
    ----------------------------------------------------------------    

同样,我有多个控制器和多个 API 以及各自的模型和服务类。您能提出更好的方法吗?

【问题讨论】:

    标签: c# oop model-view-controller


    【解决方案1】:

    拥有太多类不应该比你定义的类太复杂更令人担忧。保持类的职责简单。

    理想情况下,您的 POCO 不应包含 CRUD 方法。在您的示例中,您的 crud 方法有一个实例方法,这意味着您必须实例化 AccountModel 才能获得 List&lt;AccountModel&gt;。没有多大意义吧?

    构建一个名为IAccountModelBusiness 的单独接口来获取您的实体。让该接口的实现接受IAccountModelDataAccessIAccountModelRepository 以将数据访问的关注点与数据管理和规则执行的关注点分开。

    更新

    根据添加到问题中的示例代码,以下是该示例的一个版本,其中应用了此答案中提到的一些技术:

    第一个类是控制器类(大概是为 ASP.Net MVC 构建的):

    public class TestController : Controller
    {
        private ITestAPI api;
    
        #warning If you want, implement and register an IHttpControllerActivator to inject the IAPI implementation and remove this constructor if you want to have DI all the way up your stack.
        public  TestController() : this(new TestAPI(new UserDataService("http://10.8.200.1/test/"), new MenuService("http://10.8.200.1/menu/"), new UserItemsService("http://10.8.200.1/items/"))) {}
    
        public TestController(ITestAPI api) { this.api = api; }
    
        public ActionResult Index(string id)
        {
            return View(this.api.GetTestModel(id));
        }   
    }
    

    上面的控制器类有一个默认构造函数,我建议使用IHttpControllerActivator 实现将默认值参数删除并移动到组合根。查看this article 了解如何执行此操作的详细信息。此类旨在成为一个瘦控制器,用于托管封装在另一个类中的真实功能。我们这样做的原因是因为 .Net Web 服务技术会随着时间而变化。在过去的 15 年中,我们拥有 Active Service Method Services (ASMX)、Windows Communication (WCF)、ASP.Net MVC 和 ASP.Net Web API。这不包括第三方服务库,例如 ServiceStack 和 NancyFX。这种设计将托管责任与主要查询和组装逻辑分开。

    下一个接口用于定义由控制器托管和公开的方法:

    public interface ITestAPI
    {
        TestModel GetTestModel(string id);
    }
    

    下一个类用于提供要托管和公开的方法的实现:

    public class TestAPI : ITestAPI
    {
        private IUserDataService    userDataService;
        private IMenuService        menuService;
        private IUserItemsService   userItemsService;
    
        public TestAPI(IUserDataService userDataService, IMenuService menuService, IUserItemsService userItemsService)
        {
            this.userDataService    = userDataService;
            this.menuService        = menuService;
            this.userItemsService   = userItemsService;
        }
    
    
        public TestModel GetTestModel(string id)
        {
            var testModel = new TestModel();
    
            testModel.UserData  = this.userDataService.GetUserData(id);
            testModel.MenuList  = this.menuService.GetMenus(id);
            testModel.UserItems = this.userItemsService.GetUserItems(id);
    
            return testModel;
        }   
    }
    

    请注意,此实现依赖于服务代理来获取用户数据、菜单和用户项,因此它定义了三个构造函数参数,用于注入这些依赖项。

    以下是TestAPI 类所需的依赖项的接口:

    public interface IUserDataService  { UserData       GetUserData(string id); }
    public interface IMenuService      { List<Menu>     GetMenus(string id); }
    public interface IUserItemsService { List<UserItem> GetUserItems(string id); }
    

    这是一个抽象的服务客户端类,它以通用的形式实现重复出现的惯用服务客户端代码:

    public abstract class BaseHttpServiceClient<TEntity, TPrimaryKey> where TEntity : class
    {
        private string remoteUri;
    
        protected BaseHttpServiceClient(string remoteUri) { this.remoteUri = remoteUri; }
    
        protected virtual TEntity GetRemoteItem(TPrimaryKey id)
        {
            TEntity testData = null;
            try
            {
                using (HttpClient httpClient = new HttpClient())
                {
                    string requestUri = this.remoteUri + id.ToString();
    
                    HttpRequestMessage testRequest = new HttpRequestMessage(HttpMethod.Get, requestUri);
    
                    HttpResponseMessage response = httpClient.SendAsync(testRequest).Result;
                    if (response.IsSuccessStatusCode)
                    {
                        var jsonData = response.Content.ReadAsStringAsync().Result;
                        testData = JsonConvert.DeserializeObject<TEntity>(jsonData);
                    }
                }
            }
            catch (Exception ex)
            {           
            }
    
            return testData;
        }
    }
    

    这里是API类所需依赖的三个实现类。请注意,它们都派生自 BaseHttpServiceClient&lt;TEntity, TPrimaryKey&gt; 抽象类,并提供它们所服务的类型以及每种类型的主要 id 属性的数据类型作为基类的类型参数。

    public class UserDataService : BaseHttpServiceClient<UserData, string>, IUserDataService
    {
        public UserDataService(string remoteUri) : base(remoteUri) {}
        public UserData GetUserData(string id) { return this.GetRemoteItem(id); }
    }
    
    public class MenuService : BaseHttpServiceClient<List<Menu>, string>, IMenuService
    {
        public MenuService(string remoteUri) : base(remoteUri) {}
        public List<Menu> GetMenus(string id) { return this.GetRemoteItem(id); }
    }
    
    public class UserItemsService : BaseHttpServiceClient<List<UserItem>, string>, IUserItemsService
    {
        public UserItemsService(string remoteUri) : base(remoteUri) {}
        public List<UserItem> GetUserItems(string id) { return this.GetRemoteItem(id); }
    }
    

    每个服务类都使用GetRemoteItem 方法来检索和返回其绑定数据对象类型的实例。

    以下是其余的课程:

    public class TestModel
    {
        public string Name{get;set;}
        public string PublisherId{get;set;}
        public string AccountType{get;set;}
    
        public UserData UserData {get;set;}         //There will be a UserData model
        public List<Menu> MenuList {get;set;}       //There will be a Menu model
        public List<UserItem> UserItems {get;set;}  //There will be a UserItem model
    }
    
    public class UserData {}
    public class Menu {}
    public class UserItem {}
    

    总体而言,上述代码网络在您的示例版本上添加了大约 8 行代码,但它添加了两层依赖注入,并能够根据需要交换不同的实现,例如用于单元测试的模拟。如果您使用 solitary tests 遵循 mockist 的单元测试风格,这将特别有用。

    这是一个dotnetfiddle,显示了此代码的可编译版本。

    【讨论】:

    • 谢谢泰瑞。是的,实例化一个类来获取它自己类型的列表是没有意义的。目前,每种 API 返回类型都有很多模型类,因为它们都是不同的。所以我为每个视图都有一个模型类,它有几个其他模型的实例。我试图理解一个好的设计来放置调用 API 和返回结果对象的逻辑。如果我将它放在 Controller 中,那么它会变得很胖,但至少所有逻辑都在一个地方。是否为每个视图和多个域模型设置逻辑类?
    • @kinjaldave 听起来您要解决的问题是维护您拥有的模型/api 数量的重要性。我对您的建议是从控制器到数据库完全实施其中的 2-3 个模型,以了解您需要处理的大部分责任和关注点;尽量遵循 SOLID 原则;并采用适当的最佳实践。如果您愿意,请使用这些模型之一的完整堆栈示例更新您的问题,我很乐意对其进行审查并提出建议。
    • @kinjaldave 另外,您可能想查看我对其他问题的回答 stackoverflow.com/questions/31646525/…,以了解如何通过应用抽象和利用参数抽象来封装您的代码来减少代码量解决您的问题的模式。
    • @kinjaldave 抱歉,我没有看到您评论的最后一行。是的,解决方案是将您的关注点分成每个 模型 的逻辑/业务类,以便可以重用它们。您还将管理和验证数据的关注点与数据 I/O 的关注点分开。本质上是控制器 IBusiness IDataAccess 数据存储
    • @kinjaldave 我已经使用示例代码的修改版本以及更改的细分更新了答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-09-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-02
    相关资源
    最近更新 更多