使用简单的c#示例的坚实的架构原则

内容 固体是什么?理解“S”- SRP(单一责任原则)理解“O”——开闭原则的理解“L”——太阳能发电(Liskov替换原则)了解“我”- ISP(接口隔离原则)理解“D”——依赖性倒置原则。修改坚实的原则 介绍 我知道有1000年的关于这个话题的文章,每个月10新文章在固体会注入更多。我写这篇文章的目标是理解固体与简单的c#示例。 任何改进本文评论框下面是开放的。 固体是什么? 固体有五个基本原则whichhelp创建良好的软件架构。固体是一个缩写:- S代表SRP(单一责任原则o代表(OCP(开闭原则)l代表太阳能发电(Liskov替换原则),我代表ISP(接口隔离原则)d代表下降(依赖性倒置原则) 让我们开始了解每个原理简单的c#示例。 理解“S”- SRP(单一责任原则) 理解固体的最好方法是通过理解它试图解决什么问题。看一看下面的代码,你能猜出问题是什么?(你不会让啤酒猜这J,因为太简单)。 好吧,让我给一个提示看catch块代码。 隐藏,复制Code

class Customer
    {
        public void Add()
        {
            try
            {
                // Database code goes here
            }
            catch (Exception ex)
            {
                System.IO.File.WriteAllText(@"c:Error.txt", ex.ToString());
            }
        }
    }

以上客户类做他不应该做的事。客户类客户datavalidations应该做的,叫客户数据访问层等,但如果你看到catch块紧密也做日志记录活动。在简单的单词含有很多的责任。 所以明天如果添加一个新的日志事件查看器我需要去改变“客户”类,这是非常奇怪的。 就像“约翰”有一个问题,为什么我需要检查“BOB”。 这也让我想起了著名的瑞士刀。如果其中一个需要更改整个集需要被打扰。不进攻我的铁杆粉丝,瑞士刀。 但如果我们可以有这些物品分离它的简单、易于维护和改变不影响另一个。同样的原则也适用于在软件架构类和对象。 所以SRP说一个类应该只有一个责任,而不是多个。如果我们应用SRP,日志记录活动转移到其他类谁只会照顾日志活动。 隐藏,复制Code

class FileLogger
    {
        public void Handle(string error)
        {
            System.IO.File.WriteAllText(@"c:Error.txt", error);
        }
    }

现在客户类可以幸福的日志记录活动委托给“FileLogger”类,他可以专注于客户相关的活动。 隐藏,复制Code

class Customer
    {
        private FileLogger obj = new FileLogger();
        publicvirtual void Add()
        {
            try
            {
                // Database code goes here
            }
            catch (Exception ex)
            {
                obj.Handle(ex.ToString());
            }
        }
    }

现在建筑的思维过程是一个进化。对于一些老年人的人看着上面SRP例子反驳,甚至可以尝试捕捉不应该由客户类,因为这不是他的工作。 是的,我们可以创建一个全局错误处理程序必须在全球。asax文件,假设您正在使用ASP。净和处理这些部分的错误,使客户类完全免费。 所以我离开你可以走多远,会让这个解决方案更好的但是现在我想保持这个简单,让你的思想把它很大程度的自由。 下面是一个伟大的评论,谈到如何借此SRP的例子。 http://www.codeproject.com/Articles/703634/SOLID-architecture-principles-using-simple-Csharp?msg=4729987#xx4729987xx 理解“O”——开闭原则 让我们继续我们的客户类示例相同。我添加了一个简单的客户类型属性的类。这个属性决定如果这是一个“黄金”奥拉“银”的客户。 根据计算相同的折扣。看一看“getDiscount”函数,返回相应的折扣。1金客户和2银客户。 想,有什么问题下面的代码。哈哈哈,看来这篇文章会让你猜冠军;-)。 好,还让我添加一个提示,看这个“如果”条件“getDiscount”功能。 隐藏,复制Code

class Customer
{
        private int _CustType;

        public int CustType
        {
            get { return _CustType; }
            set { _CustType = value; }
        }

        public double getDiscount(double TotalSales)
        {
                if (_CustType == 1)
                {
                    return TotalSales - 100;
                }
                else
                {
                    return TotalSales - 50;
                }
        }
}

问题是如果我们添加一个新客户类型,我们需要去添加一个“如果”条件“getDiscount”功能,换句话说,我们需要改变客户类。 如果我们改变一次又一次的客户类,我们需要确保前面的条件与新再次测试,现有客户的引用这个类是正常工作。 换句话说我们是“修改”当前的客户代码对于每一个变化,我们每次修改之前我们需要确保所有的功能和联系客户工作。 而不是“修改”我们如何去“扩展”。换句话说每次添加新客户类型需要我们创建一个新类,如下所示。所以无论当前的代码,我们只需要测试和检查新类。 隐藏,复制Code

class Customer
{
        public virtual double getDiscount(double TotalSales)
        {
            return TotalSales;
        }
}

  class SilverCustomer : Customer
    {
        public override double getDiscount(double TotalSales)
        {
            return base.getDiscount(TotalSales) - 50;
        }
    }

藏e,复制Code

class goldCustomer : SilverCustomer
    {
        public override double getDiscount(double TotalSales)
        {
            return base.getDiscount(TotalSales) - 100;
        }
    }

简单地说,对于任何新的修改,“Customer”类现在都是关闭的,但是当新的客户类型被添加到项目中时,它对于扩展是打开的。 理解“L”- LSP (Liskov置换原理) 让我们继续同一个客户。假设我们的系统要计算查询折扣。现在询盘不是真正的客户,他们只是线索。因为它们只是线索,所以我们暂时不希望将它们保存到数据库中。 因此,我们创建一个名为Enquiry的新类,它继承自“Customer”类。我们对查询提供一些折扣,以便它们可以转换为实际客户,我们覆盖了“添加”方法,但有一个例外,以便没有人可以向数据库添加查询。 隐藏,复制Code

class Enquiry : Customer
    {
        public override double getDiscount(double TotalSales)
        {
            return base.getDiscount(TotalSales) - 5;
        }

        public override void Add()
        {
            throw new Exception("Not allowed");
        }
    }

如果您将当前客户继承层次结构可视化,它看起来如下所示。换句话说,“Customer”是父类,“Gold”、“Silver”和“Enquiry”是子类。 因此,根据多态性规则,我的父“客户”类对象可以指向任何it子类对象,即“金”,“银”或“查询”在运行时没有任何问题。 例如,在下面的代码中,您可以看到我创建了一个“Customer”的列表集合,并且多亏了多态性,我可以毫无问题地将“Silver”、“Gold”和“Enquiry”Customer添加到“Customer”集合中。 由于多态,我还可以使用父Customer对象浏览“Customer”列表,并调用“Add”方法,如下面的代码所示。 现在再一次让我来逗你们,这里有一个小问题,想,想,想。 提示:-观察查询对象在“FOR EACH”循环中浏览和调用的时间。 , 隐藏,复制Code

List<Customer> Customers = new List<Customer>();
Customers.Add(new SilverCustomer());
Customers.Add(new goldCustomer());
Customers.Add(new Enquiry());

 foreach (Customer o in Customers)
 {
                o.Add();
 }
}

根据继承层次结构,“Customer”对象可以指向它的任何子对象,我们不期望出现任何异常行为。 但是当" Enquiry "对象的" Add "方法被调用时,它会导致以下错误,因为我们的" Equiry "对象将查询保存到数据库中,因为它们不是实际的客户。 现在正确地阅读下面的段落来理解这个问题。如果你不明白下面的段落,请读两遍。 换句话说,“询价”有折扣计算,它看起来像一个“客户”,但它不是一个客户。因此父对象不能无缝地替换子对象。换句话说,“Customer”不是“Enquiry”类的实际父类。“查询”是一个完全不同的实体。 所以LISKOV原则说父对象应该很容易替换子对象。因此,要实现LISKOV,我们需要创建两个接口,一个用于折扣,另一个用于数据库,如下所示。 隐藏,复制Code

interface IDiscount
{
        double getDiscount(double TotalSales);
}


interface IDatabase
{
        void Add();
}

现在,“Enquiry”类将只实现“IDiscount”,因为它对“Add”方法不感兴趣。 隐藏,复制Code

class Enquiry : IDiscount
    {
        public  double getDiscount(double TotalSales)
        {
            return TotalSales - 5;
        }
    }

而“Customer”类将同时实现“IDiscount”和“IDatabase”,因为它还希望将客户持久化到数据库中。 隐藏,复制Code

class Customer : IDiscount, IDatabase
   {


       private MyException obj = new MyException();
       public virtual void Add()
       {
           try
           {
               // Database code goes here
           }
           catch (Exception ex)
           {
               obj.Handle(ex.Message.ToString());
           }
       }

       public virtual double getDiscount(double TotalSales)
       {
           return TotalSales;
       }
   }

现在没有混淆了,我们可以创建一个“Idatabase”接口列表,并向其中添加相关的类。如果我们错误地将“Enquiry”类添加到列表中,编译器会报错,如下面的代码片段所示。 , 理解“I”- ISP(界面分离原则) 现在假设我们的customer类已经成为一个超级热门组件,它在1000个客户机上使用,它们都非常喜欢使用customer类。 , 现在让我们假设一些新客户提出一个要求,说我们也想要一个方法来帮助我们“读取”客户数据。因此,高度热情的开发人员会希望更改“IDatabase”接口,如下所示。 但你能猜到,我们这样做是做了一件可怕的事情吗? 提示:-考虑一下这个变化对上面图片的影响。 隐藏,复制Code

interface IDatabase
{
        void Add(); // old client are happy with these.
voidRead(); // Added for new clients.
}

如果你设想新的要求已经出现,你有两种客户的:- 想要的就用“添加”方法。另一个人想用“添加”+“阅读”。 现在,通过更改当前接口,您正在做一件可怕的事情,即使当客户端对“Read”方法不感兴趣时,也会干扰1000个满意的当前客户端。你强迫他们使用“阅读”方法。 因此,更好的方法是将现有客户留在他们自己的甜蜜世界中,并分别为新客户提供服务。 因此,更好的解决方案是创建一个新接口,而不是更新当前接口。因此,我们可以保持当前接口“IDatabase”不变,并使用“Read”方法添加一个新接口“IDatabaseV1”,“V1”代表版本1。 隐藏,复制Code

interface IDatabaseV1 : IDatabase // Gets the Add method
{
Void Read();
}

现在,您可以创建实现“Read”方法的新类,并满足新客户端的需求,而旧客户端对没有“Read”方法的旧接口保持原样并感到满意。 隐藏,复制Code

class CustomerwithRead : IDatabase, IDatabaseV1
    {

public void Add()
{
	Customer obj = new Customer();
Obj.Add();
}
	Public void Read()
	{
	// Implements  logic for read
}
    }

所以老客户端将继续使用“IDat”当新客户端可以使用“IDatabaseV1”接口时。 隐藏,复制Code

IDatabase i = new Customer(); // 1000 happy old clients not touched
i.Add();

IDatabaseV1 iv1 = new CustomerWithread(); // new clients
Iv1.Read();

理解“D”依赖倒置原则 如果您还记得的话,在我们的customer类中,我们创建了一个logger类来满足SRP。接下来,我们假设创建了新的Logger风味类。 隐藏,复制Code

class Customer
    {
        private FileLogger obj = new FileLogger();
        public virtual void Add()
        {
            try
            {
                // Database code goes here
            }
            catch (Exception ex)
            {
                obj.Handle(ex.ToString());
            }
        }
    }

为了控制事情,我们创建了一个公共接口,并使用这个公共接口将创建新的日志程序风格。 隐藏,复制Code

interface ILogger
{
        void Handle(string error);
}

下面是三种logger口味,还可以添加更多。 隐藏,复制Code

class FileLogger : ILogger
    {
        public void Handle(string error)
        {
            System.IO.File.WriteAllText(@"c:Error.txt", error);
        }
    }

隐藏,复制Code

class EverViewerLogger : ILogger
    {
        public void Handle(string error)
        {
            // log errors to event viewer
        }
    }

隐藏,复制Code

class EmailLogger : ILogger
  {
      public void Handle(string error)
      {
          // send errors in email
      }
  }

现在,根据配置设置,不同的日志记录器类将在给定时刻使用。因此,为了达到同样的目的,我们保持了一个简单的IF条件,它决定使用哪个日志记录器类,参见下面的代码。 小测验时间到了,这里的问题是什么? 提示:-观看捕获块代码。 隐藏,复制Code

class Customer : IDiscount, IDatabase
    {
        private IException obj; 

public virtual void Add(int Exhandle)
        {
            try
            {
                // Database code goes here
            }
            catch (Exception ex)
            {
                if (Exhandle == 1)
                {
                    obj = new MyException();
                }
                else
                {
                    obj = new EmailException();
                }
                obj.Handle(ex.Message.ToString());
            }
        }

上面的代码再次违反了SRP,但这一次方面有所不同,它是关于决定应该创建哪些对象。现在,决定创建哪些实例不是“Customer”对象的工作,他应该只关注与Customer类相关的功能。 如果你仔细观察,最大的问题是“新的”关键字。他对需要创建的对象承担额外的责任。 因此,如果我们把这个责任倒置或委托给其他人,而不是客户类来做,这将在一定程度上真正解决问题。 , 下面是实现了反转的修改代码。我们已经打开了构造函数的口,我们希望其他人来传递对象,而不是由customer类来做。因此,现在由使用customer对象的客户机负责决定注入哪个Logger类。 隐藏,复制Code

class Customer : IDiscount, IDatabase
 {
        private Ilogger obj;
        public Customer(ILogger i)
        {
            obj = i;
        }
}

因此,现在客户端将注入Logger对象,并且客户对象现在不再受那些决定注入哪个Logger类的IF条件的影响。这是实体依赖倒置原则中的最后一个原则。 客户类将依赖对象的创建委托给使用依赖对象的客户,从而使客户类集中于其工作。 隐藏,复制Code

IDatabase i = new Customer(new EmailLogger());

修改坚实的原则 S代表SRP(单一责任原则):-一个类应该只负责一个责任。 O代表OCP(开闭原则):-扩展应该优先于修改。 L代表LSP (Liskov substitution principle):-在运行时多态期间,父类对象应该能够无缝地引用子对象。 I代表ISP(接口隔离原则):——如果客户端不需要接口,就不应该强迫它使用。 D代表DIP(依赖倒置原则):-高层模块不应该依赖于低层模块,而应该依赖于抽象。 如果您已经完成了这篇文章,那么接下来的逻辑步骤将是学习GOF设计模式,这里有一篇同样的文章,希望您喜欢。 一旦你完全理解了,下一步就是学习设计模式。下面是一个很好的视频,教你在8小时内设计一个项目的模式。 本文转载于:http://www.diyabc.com/frontweb/news2082.html

原文地址:https://www.cnblogs.com/Dincat/p/13457001.html