🔍
📢

面向对象三大特性(封装、继承、多态)及SOLID设计原则

前言

  今天我们来谈谈面向对象的三大特性--封装、继承、多态,以及面向对象设计的头五大原则(SOLID五大原则)

 

封装

   被定义为"把一个或多个项目封闭在一个物理的或者逻辑的包中"。在面向对象程序设计方法论中,封装是为了防止对实现细节的访问。封装只公开某些对外接口,隐藏具体实现细节。增加了一定的安全性,防止信息的泄露以及破坏。

  讲到封装,我们又得提下访问修饰符了。

    • public:所有对象都可以访问;
    • private:对象本身在对象内部可以访问;
    • protected:只有该类对象及其子类对象可以访问
    • internal:同一个程序集的对象可以访问;
    • protected internal:访问限于当前程序集或派生自包含类的类型。
 

继承

  继承是软件复用的一种形式。使用继承可以复用现有类的数据和行为,为其赋予新功能而创建出新类。 

  在现有类(基类、父类)上建立新类(派生类、子类)的处理过程称为继承。派生类能自动获得基类的除了构造函数和析构函数以外的所有成员,可以在派生类中添加新的属性和方法扩展其功能

  这里继承又可分为以下系列:

    • 单重继承:表示一个类可以派生自一个基类,C#采用此继承
    • 多重继承:多重继承允许一个类派生自多个类,C#不支持多重继承,但允许接口的多重继承
    • 多层继承:多层继承允许有更大的层此结构,类B派生自类A,类C派生自类B,其中,类B也称为中间基类,C#支持它,也很常用。
    • 接口继承:允许接口多重继承
 

多态

  多态指在程序设计中存在同名不同方法的存在,主要通过子类对父类的覆盖来实现多态,设计原则之一就是要依赖于抽象,而不依赖于具体,增加灵活性。多态就是为了体现这一原则。

 

实例讲解

  这里我们假设一个场景,对图形的面积进行计算。在这里我们就抽象一个基类,形状。然后其他的设计都来继承它。

    类设计

    

 
    /// <summary>
    /// 抽象类
    /// </summary>
    public abstract class Shape
    {
        private string ShapeName { get; set; }
        public Shape(string shapeName)
        {
            this.ShapeName = shapeName;
        }

        /// <summary>
        /// 计算面积
        /// </summary>
        /// <returns></returns>
        public abstract double CalculateArea();
    }

 

    /// <summary>
    /// 长方形
    /// </summary>
    public class Rectangle:Shape
    {
        /// <summary>
        ////// </summary>
        public double Longm { get; set; }
 

        /// <summary>
        ////// </summary>
        public double Widem { get; set; }
        public Rectangle():base("Rectangle")
        {
            Longm = 0;
            Widem=0;
        }

        public override double CalculateArea()
        {
            return Longm * Widem;
        }
    }
    /// <summary>
    /// 圆形
    /// </summary>
    public class Circle: Shape
    {
        /// <summary>
        /// 半径
        /// </summary>
        public double R { get; set; }

       
        public Circle(): base("Circle ")
        {
            R = 0;
        }

        public override double CalculateArea()
        {
            return Math.PI*R*R;
        }
    }
 

   调用

   

 
class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("请选择计算面积的图形:长方形(A)/圆形(B)");
            string answer = Console.ReadLine();
            if (answer=="A")
            {
                double longm=0;
                double widem = 0;
                try
                {
                    Console.WriteLine("请输入长:");
                     longm = double.Parse(Console.ReadLine());
                    Console.WriteLine("请输入宽:");
                     widem = double.Parse(Console.ReadLine());
                }
                catch (Exception)
                {
                    Console.WriteLine("请输入数字!");
                }

                Rectangle rectangle = new Rectangle();
                rectangle.Longm = longm;
                rectangle.Widem = widem;
                Console.WriteLine($"此长方形的面积是{rectangle.CalculateArea()}");
            }
            else
            {
                double r=0;
                try
                {
                    Console.WriteLine("请输入半径:");
                 r = int.Parse(Console.ReadLine());
                }
                catch (Exception)
                {
                    Console.WriteLine("请输入数字!");
                }
                Circle circle = new Circle();
                circle.R = r;
                Console.WriteLine($"此圆形的面积是{circle.CalculateArea()}");
            }
        }
    }
 
 

总结

   本个案例实际作用不是很大,主要是方便讲解理解封装继承多态,在实例中,对图形的名称封装,抽象一个抽象类图形基类,圆形和长方形继承此基类。override 重写实现面积计算的多态。更多的还是需要在实际项目中实际运用的。

 

 

 

SOLID原则是面向对象编程和面向对象设计的头五大原则。学习及应用这五大原则可以构建一个易于维护和扩展的应用程序,我们一起看看到底是那五大原则。

  • S--单一责任原则(SRP) --Single Responsibility Principle
  • O--开放封闭原则(OCP)-- Open-Closed  Principle
  • L--里式替换原则(LSP)-- Liskov Substitution Principle
  • I –- 接口分离原则(ISP)--Interface Segregation Principle
  • D–-依赖倒置原则(DIP)-- Dependency Inversion Principle

 

一、单一责任原则(SRP)

单一责任原则指出当需要修改某个类的时候原因有且只有一个。也就是说一个类应该只负责一件事情。当这个类需要去做其他的事情的时候,就需要分解这个类。如果把多个功能放在一个类中要它负责,

那么各个功能之间会形成关联,改变其中一个功能可能会牵连其他的功能的改变,这样有需要花费时间和人力对其他功能的改变进行测试,保证其他功能的完整。

Ex:

 一个有关长方形的类,长、宽、面积。后来需要增加正方形,就继续使用长方形的类,使用的时候长=宽。这一种情况违背了SRP原则,一个类只负责一件事情,这个时候应该新建一个正方形的类。

二、开放封闭原则(OCP)

开放封闭原则指的是程序模块应该遵循关闭修改,开放扩展。这里与单一责任原则很好的联系在了一起。一个类只负责一件事情。在程序模块中当业务更改或新增的时候不应该更改现有的代码行为,

应该转向开放扩展。其中一个方法是通过抽象方法,然后继承已达到扩展的想法。

Ex:

还是上面那个例子,最开始是计算长方形的面积,然后增加了正方形面积的计算。遵循关闭修改开放扩展的原则,不修改现有的代码行为。将计算方法抽象继承已扩展。新加正方形计算方法。

复制代码
public interface Calculate
    {
         decimal CalculateArea(decimal longs, decimal wide=0);
    }

    public class Rectangle : Calculate
    {
        public   decimal CalculateArea(decimal  longs,decimal  wide)
        {
            decimal s = longs * wide;
            return s;
        }
    }

    public class Square : Calculate
    {
        public decimal CalculateArea(decimal longs, decimal wide)
        {
            decimal s = longs * longs;
            return s;
        }
    }
复制代码

 

三、里氏替换原则(LSP)

子类型必须可替代其基类型 –一个对象出现的地方都可以由其子类代替并且不会出错,即是符合里氏替换原则的。

Ex:

       狗和鸟同时都具备很多相同特征,可以走、跑、叫,以鸟作为基类,狗作为子类,会出现子类不能替换基类的情况,基类鸟可以飞,但是子类狗不能。这样不就符合里氏替换原则。可以考虑以狗走位基类,鸟作为子类,然后鸟扩展一个飞的属性。或者两者都作为子类,抽象出一个基类,动物类。满足子类可以任意替换基类的情况都是符合里氏替换原则的。

   

复制代码
class Program
    {
        static void Main(string[] args)
        {
            Animal animal = new Dog();
            Console.WriteLine(animal.Walk());
            Console.WriteLine(animal.Run());
            Console.WriteLine(animal.Fly());
            Console.WriteLine(animal.MakeNoise());
            Console.ReadLine();
        }
    }
 

    public class Animal
    {
        public string Walk()
        {
            return "Move feet";
        }

 
        public string Run()
        {
            return "Move feet quickly";
        }
 

        public virtual string Fly()
        {
            return null;
        }

 
        public virtual string MakeNoise()
        {
            return null;
        }
    }

 
    public class Dog : Animal
    {
        public override string MakeNoise()
        {
            return "Bark";
        }
    }
 

    public class Bird : Animal
    {
        public override string MakeNoise()
        {
            return "Chirp";
        }

 
        public override string Fly()
        {
            return "Flag wings";
        }
    }
复制代码

 

 

四、接口分离原则(ISP)

接口分离原则—client不应该被强迫依赖它不使用的方法,表明方法是分开或者隔离的。这个原则还强制实现高凝聚力,让您更好地理解,更强大的类和低耦合,更容易维护,更容易抵抗变化(即不太可能引入错误)。

Ex:

复制代码
public interface Animal
    {
         string Run();
         string Fly();
    }

    public class Dog : Animal
    {
        public string Fly()
        {
            return string.Empty;
        }
 
        public string Run()
        {
            return "小狗,快跑";
        }
    }

 
    public class Bird : Animal
    {
        public string Fly()
        {
            return "小鸟,快飞";
        }
 

        public string Run()
        {
            return "小鸟,快跑";
        }
    }
复制代码

 

在这段代码中,鸟和狗同时继承了动物,但是在狗实现接口的时候,Fly方法没有做任何操作。这里显然违背了接口分离原则,强迫了Dog类依赖了其Fly方法。

改进方法,可以将动物接口修改成两个接口,AnimalFly接口和AnimalRun接口。这样就遵循了其规则

 

五、依赖倒置原则(DIP)

       依赖倒置原则-也是最后一个原则了。其原则指出—一个高级模块不应依赖于低级模块,两个都应该取决于抽象。抽象不应该依赖具体细节,细节应该依赖于抽象。

       在这里可以发现依赖倒置原则和前几天讲过的依赖注入的原则十分相似。

 

六、总结

      

SRP

单一职责原则

一个类应只负责一件事情

OCP

开放封闭原则

封闭修改,开放扩展

LSP

里氏替换原则

一个对象可由其子类代替

ISP

接口分离原则

客户不应被强迫依赖它不使用的方法

DIP

依赖反转原则

抽象不依赖具体,具体依赖于抽象

 

S.O.L.I.D 原则是非常有价值的五大原则,在创建和设计一个应用的时候应用这些原则,你会创建一个非常优秀的项目。