Software layered architecture

本文深入探讨了在软件开发过程中分层设计的重要性及其常见问题,提出了通过依赖注入、接口抽提、延迟加载等手段来解决耦合度高、扩展性差等问题的策略。并介绍了如何利用分层思想提高代码的灵活性和单元测试能力。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在开发过程中,我们经常对业务逻辑层分层,分层的好处显而易见:各司其职,分而治之!很多时候,分层的时候对于为什么要分层,为什么要分这样几层想得不够清楚,就容易造成为了分层而分层,层与层之间仅仅是简单的调用关系,而没有其他更多的内容。层与层之间的调用方式也有多钟,最常见的就是通过在上层类的方法中创建所依赖对象的实例,然后调用底层的方法,这种调用造成耦合性高,非常不方便进行单元测试。综合以上诸多的考虑,在软件开发过程中,开发者往往总结出很多最佳实践。

为什么要分层

关于这个问题,开发者通常都能达成共识。不同层次所关注的事情不同,开发者能够独立地关注自己开发所在的层次。然而,在实际开发过程中,通常情况是需要增加新的功能了,在各层加入自己的业务逻辑,只不过这些写法比较类似,比较节省时间,这时候我们再想想,我们当初为什么要分层呢?这时候答案就不是那么清晰了。

public class UserManager
{
    public bool AddUser(SystemUser systemUser)
    {
	UserAction userAction = new UserAction();
        return userAction.AddUser(systemUser);
    }
}
假设我们有UserManager,UserAction,UserOperator三层,那么这三层之间可以通过如上所示的例子进行调用。其实,这样做完全没有问题,并且只要UserAction的AddUser方法不发生变化,这里的代码不需要修改,从一定程度上,分层降低了耦合度。可是如果我们从如下几个方面考虑:
1)对象创建的连锁反应

这个例子中我们的UserAction创建非常简单,在实际的逻辑中,创建一个对象的同时,还需要创建该对象所依赖的对象,如果UserAction创建失败,整个UserManager也会创建失败,导致我们整个模块的崩溃,这种“多米诺骨牌”效应非常可怕,后果是非常严重的。

2)对象创建的延时

在对象创建的过程中,需要加载的资源很多的时候,延时效应非常明显,极大地影响了代码的效率。

3)扩展性

如果业务逻辑发生变化,我们的UserAction只能在某些情况下适用,在其他情形下需要一个新的Action。这时候,我们又不能直接替换掉UserAction,也就是说UserManager应该将调用哪个Action的选择权交出来,这时候我们的代码灵活度就不够了,这时候我们需要怎解决呢?

4)单元测试

在单元测试中,通常我们剥离对象所依赖的对象,如果所依赖的对象与被测试对象耦合度太高,就很难剥离(如例子所示)。

针对以上几个问题,我们的解决办法如下:

1)解决对象耦合度过高

依赖注入的方式,将控制权反转,交给调用者,让调用者去创建对象。

2)解决扩展性和无法单元测试

抽提接口出来,注入抽象的接口,在调用时才注入具体的对象。

3)延时

采用延迟加载机制

在开发时,我们通常分成Operator,Action,Manager三层,每一层都注入上一层的抽象对象,这样就能解决我们遇到的绝大多数问题。

Operator层

IUserOperator.cs

namespace AttributeDemo
{
    public interface IUserOperator
    {
        bool AddUser(SystemUser systemUser);
    }
}
UserOperator.cs

namespace AttributeDemo
{
    public class UserOperator : IUserOperator
    {
        [Log(Level.Info)]
        public bool AddUser(SystemUser systemUser)
        {
            Console.WriteLine("Add User with name:[" + systemUser.Name + "] successfully.");
            return true;
        }
    }
}
Action层

IUserAction层

namespace AttributeDemo
{
    public interface IUserAction
    {
        bool AddUser(SystemUser systemUser);
    }
}
UserAction

using System;
using System.Linq;

namespace AttributeDemo
{
    public class UserAction:IUserAction
    {
        private readonly IUserOperator _userOperator;
        public UserAction(IUserOperator userOperator)
        {
            _userOperator = userOperator;
        }

        public UserAction():this(new UserOperator())
        {
            Console.WriteLine("UserAction constructed.");
        }

        public bool AddUser(SystemUser systemUser)
        {
            if(systemUser.Id == null || systemUser.Name == null) throw  new Exception("Arguments property can not be null!");
            bool flag = false;
            const string methodName = "AddUser";
            //System.Reflection.MemberInfo info = _userOperator.GetType();
            //var logAttribute = (LogAttribute) Attribute.GetCustomAttribute(info, typeof (LogAttribute));
            var attribute =
                typeof(UserOperator).GetMethod(methodName)
                    .GetCustomAttributes(typeof (LogAttribute), false)
                    .FirstOrDefault() as LogAttribute;
            if (attribute == null) return false;
            var level = attribute.LogLevel;

            Console.WriteLine(level + ":Method [" + methodName + "]" + " is begining.");
            flag = _userOperator.AddUser(systemUser);
            Console.WriteLine(level + ":Method [" + methodName + "]" + " is ending.");

            //Console.WriteLine(logAttribute.LogLevel + ":Method " + GetType() + " is begining.");
            //flag = _userOperator.AddUser(systemUser);
            //Console.WriteLine(logAttribute.LogLevel + ":Method " + GetType() + " is ending.");
            return flag;
        }
    }
}
UserManager层

IUserManager

namespace AttributeDemo
{
    public interface IUserManager
    {
        bool AddUser(SystemUser systemUser);
    }
}
UserManager

namespace AttributeDemo
{
    public class UserManager : IUserManager
    {
        private readonly IUserAction _userAction;

        public UserManager(IUserAction userAction)
        {
            this._userAction = userAction;
        }

        public UserManager() : this(new UserAction())
        {

        }


        public bool AddUser(SystemUser systemUser)
        {
            return this._userAction.AddUser(systemUser);
        }
    }
}
这里我们用到了Attribute及枚举Level

enum Level

namespace AttributeDemo
{
    public enum Level
    {
        Info,
        Warn,
        Err,
    }
}
LogAttribute

using System;

namespace AttributeDemo
{
    [AttributeUsage(AttributeTargets.Method)]
    public class LogAttribute : Attribute
    {
        public Level LogLevel { set; get; }

        public LogAttribute(Level level)
        {
            LogLevel = level;
        }
    }
}
这里我们使用了LogAttribute属性,只需要在方法上加上Attrinute就可以在Action层通过AOP的方式输出方法开始和结束的日志。这样我们在分层的时候目的就明确了,扩展性也更强了,因为我们可以自己根据需要注入Action(实现IAction接口即可),同时我们还可以选择使用默认的Action或者自己注入Action。因为每一层提供了一个默认的无参数的构造函数和有参数的构造函数。

public UserManager(IUserAction userAction)
{
      this._userAction = userAction;
}

public UserManager() : this(new UserAction())
{

}

综合以上两种机制,更加灵活的方式也更好地支持了单元测试,因为单元测试可以更好地剥离被测试的对象所依赖的对象。调用其实非常简单:

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;

namespace AttributeDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            //IUserOperator userOperator = new UserOperator();
            //var userAction = new UserAction(userOperator);
            var userManager = new UserManager();
            userManager.AddUser(new SystemUser(1, "justin"));

            Console.ReadKey();
        }
    }
}

关于Action的单元测试


为什么是三层

底层的实现通常很少替换,是最稳固的一层,中层是改变最多的,上层改变也是很少的,通常我们替换中间层,这样能够确保一个非常稳固的结构。

从软件的角度来说,中间层是最为重要的一层,我们的Action里面做了参数的校验和日志的输出,是我们的核心层,三层的思想在这里得到了很好的体现。

总结

其实降低耦合度有一个很好的解决方案,就是Spring.NET,其实是从Spring发展而来,通过配置的方式,进行注入,这里就不再探讨,有时间再介绍!

代码下载(免费):http://download.youkuaiyun.com/detail/afandaafandaafanda/9080533
















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值