C#--抽象类与开闭原则(接口、抽象类、SOLID、单元测试、反射)之上篇

 一、什么是接口和抽象类

1.接口和抽象类都是“软件工程产物”

2.具体类→抽象类→接口:越来越抽象,内部实现的东西越来越少

例1:抽象类

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

namespace ConsoleApp50
{
    class Program
    {
        static void Main(string[] args)
        {
            Vehicle v = new Car();
            v.Run();

            Vehicle raceCar = new RaceCar();
            raceCar.Stop();
            raceCar.Run();
        }
    }

    abstract class Vehicle
    {
        public void Stop()
        {
            Console.WriteLine("Stopped!");
        }

        //public virtual void Run()
        //{
        //    Console.WriteLine("Vehicle is running !");
        //}
        //其实我们并不调用这个方法,所以它的方法体都没有意义,所以把它变成抽象方法,把类变成抽象类
        //抽象类的派生类,如果不实现未完成的方法,自己也要变成抽象类

        public abstract void Run();
    }

    class Car:Vehicle
    {
        public override void Run()
        {
            Console.WriteLine("Car is running !");
        }
    }

    class Truck:Vehicle
    {
        public override void Run()
        {
            Console.WriteLine("Truck   is running !");
        }
    }

    class RaceCar : Vehicle
    {
        public override void Run()
        {
            Console.WriteLine("RaceCar is running !");
        }
    }



    //abstract class Student//抽象类无法被实例化,只有两个用处:1.作为基类 2.作为基类对象去创建变量再引用一个子类的实例
    //{
    //    abstract public void Study();//抽象方法,又称纯虚方法,一旦类里面有了抽象方法就会变成抽象类,类前面也要加上abstract
    //}
}
/*
*为做基类而生的“抽象类”与“开放关闭原则” 
* 开闭原则:封装那些不变的、稳定的、固定的和确定的成员;而把那些不确定的、有可能改变的成员声明为抽象成员,留给子类去实现
* 
 */

例2:接口

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

namespace ConsoleApp50
{
    class Program
    {
        static void Main(string[] args)
        {
            Vehicle v = new Car();
            v.Run();

            Vehicle raceCar = new RaceCar();
            raceCar.Stop();
            raceCar.Run();
        }
    }
    /// <summary>
    /// 纯抽象类其实可以写成接口,把“abstract class”替换成“interface”;接口里的方法是默认public和abstract的所以要把“public abstract”都删掉
    /// 接口名称一般I开头名词结尾
    /// </summary>

   
    interface IVehicleBase
    {
        void Stop();
        void Fill();
        void Run();
    }

    abstract class Vehicle:IVehicleBase
    {
        public void Stop()
        {
            Console.WriteLine("Stop!");
        }
         
        public void Fill()
        {
            Console.WriteLine("Pay and fill...");
        }

        public abstract void Run();//没有实现的方法就往下推
    }

    class Car : Vehicle
    {
        public override void Run()
        {
            Console.WriteLine("Car is running !");
        }
    }

    class Truck:Vehicle
    {
        public override void Run()
        {
            Console.WriteLine("Truck   is running !");
        }
    }

    class RaceCar : Vehicle
    {
        public override void Run()
        {
            Console.WriteLine("RaceCar is running !");
        }
    }

3.抽象类是为完全实现逻辑的类(可以有字段和非public成员,它们代表了“具体逻辑”)

4.抽象类为复用而生:专门作为基类来使用,也具有解耦功能

5.封装确定的,开放不确定的,推迟到合适的子类中去实现

6.接口是完全来实现逻辑的“类”(“纯虚类”;只有函数成员;全员public)

7.接口为解耦而生:“高内聚,低耦合”,方便单元测试

8.接口是一个“协约”,早已为工业生产所熟知(有分工必有合作,有协作必有协约)

9.它们都不能实例化,只能用来声明变量、引用具体类(concrete class)的实例

‘10.SOLID设计原则分别是:

S:单一职责原则(Single Responsibility)

O:开闭原则(Open Closed)

L:里氏代换原则(Liskov Substitution)

I:接口隔离原则(Interface Segregation)

D:依赖倒置原则(Dependency Inversion) ’

 

二、接口与单元测试

1.接口的产生:自底向上(重构)、自顶而下(设计)

例1:没有接口,不用泛型的情况下(非常麻烦,要写很多方法)

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

namespace ConsoleApp51
{
    class Program
    {
        static void Main(string[] args)
        {
            int[] nums1 = new int[] { 1, 2, 3, 4, 5 };
            ArrayList nums2 = new ArrayList { 1, 2, 3, 4,5};

            Console.WriteLine(Sum(nums1));
            Console.WriteLine(Avg(nums1));

            Console.WriteLine(Avg(nums1));//无法将ArrayList类型转换成int类型,需要类型转换
        }

        static int Sum(int[]nums)
        {
            int sum = 0;
            foreach (var num in nums)
            {
                sum += num;
            }
            return sum;
        }

        static double Avg(int[] nums)
        {
            int sum = 0;
            double count = 0;
            foreach (var num in nums)
            {
                sum += num;
                count++;
            }
            return sum / count;
        }

        static int Sum(ArrayList nums)
        {
            int sum = 0;
            foreach (var num in nums)
            {
                sum += (int)num;//因为ArrayList是object类型的,需要做类型转换
            }
            return sum;
        }

        static double Avg(ArrayList nums)
        {
            int sum = 0;
            double count = 0;
            foreach (var num in nums)
            {
                sum += (int)num;
                count++;
            }
            return sum / count;
        }
    }
}

例2:根据上个例子,Sum、Avg两个方法可以写成接口,要求是传进来的数组能被foreach迭代就行 ,所以可以使用IEnumerable

 

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

namespace ConsoleApp51
{
    class Program
    {
        static void Main(string[] args)
        {
            int[] nums1 = new int[] { 1, 2, 3, 4, 5 };
            ArrayList nums2 = new ArrayList { 1, 2, 3, 4,5};

            Console.WriteLine(Sum(nums1));
            Console.WriteLine(Avg(nums1));

            Console.WriteLine(Avg(nums1));//无法将ArrayList类型转换成int类型,需要类型转换
        }

        static int Sum(IEnumerable nums)//IEnumerable:公开枚举数,该枚举数支持在泛型上进行简单迭代
        {
            int sum = 0;
            foreach (var num in nums)
            {
                sum += (int)num;
            }
            return sum;
        }

        static double Avg(IEnumerable nums)
        {
            int sum = 0;
            double count = 0;
            foreach (var num in nums)
            {
                sum += (int)num;
                count++;
            }
            return sum / count;
        }
    }
}

2.C#中接口的实现(隐式、显式,多接口)

例:紧耦合的情况

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

namespace ConsoleApp52
{
    class Program
    {
        static void Main(string[] args)
        {
            var engine = new Engine();
            var car = new Car(engine);
            car.Run(3);
        }
    }
    /// <summary>
    /// 紧耦合的情况,被依赖的类出问题就会影响到其他类的功能。
    /// 而接口就是为了尽量避免这种情况而出现的
    /// </summary>
    class Engine
    {
        public int RPM { get; private set; }
        public void Work(int gas)
        {
            RPM = 1000 * gas;
        }
    }
    
    class Car
    {
        private Engine  _engine;
        public Car (Engine engine)
        {
            _engine = engine;
        }

        public int Speed { get; private set; }
        public void Run(int gas)
        {
            _engine.Work(gas);
            Speed = _engine.RPM / 100;
        }
    }
}

例:接口:你只管调用,内部逻辑你可以不知道。接口就是为了松耦合而存在

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

namespace ConsoleApp53
{
    class Program
    {
        static void Main(string[] args)
        {
            var user = new PhoneUser(new NokiaPhone());
            user.UsePhone();
        }
    }

    class PhoneUser
    {
        private IPhone _phone;
        public PhoneUser (IPhone phone)
        {
            _phone = phone;
        }

        public void UsePhone()
        {
            _phone.Dail();
            _phone.PickUp();
            _phone.Send();
            _phone.Receive();
        }
    }

   interface IPhone
    {
        void Dail();
        void PickUp();
        void Send();
        void Receive();
    }

    class NokiaPhone : IPhone
    {
        public void Dail()
        {
            Console.WriteLine("Nokia is calling...");
        }

        public void PickUp()
        {
            Console.WriteLine("Hello!This is Lin!");
        }

        public void Receive()
        {
            Console.WriteLine("Nokia message ring ...");
        }

        public void Send()
        {
            Console.WriteLine("Hello");
        }
    }

    class EricssonPhone : IPhone
    {
        public void Dail()
        {
            Console.WriteLine("Ericsson is calling...");
        }

        public void PickUp()
        {
            Console.WriteLine("Hello!This is Lin!");
        }

        public void Receive()
        {
            Console.WriteLine("Ericsson message ring ...");
        }

        public void Send()
        {
            Console.WriteLine("Good Evening!");
        }
    }
}

 

3.语言对面向对象设计的內建支持:依赖反转,接口隔离,开/闭原则...

例:紧耦合,显然不是我们想要的;需要接口进行解耦

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

namespace ConsoleApp54
{
    class Program
    {
        static void Main(string[] args)
        {
            var fan = new DeskFan(new PowerSupply());
            Console.WriteLine(fan.Work());
        }
    }

    class PowerSupply
    {
        public int GetPower()
        {
            return 100;//需要一直不断修改电力,这不是我们想要的结果                                                                                 
        }
    }

    class DeskFan 
    {
        private PowerSupply _powerSupply;//紧耦合
        public DeskFan(PowerSupply powerSupply)
        {
            _powerSupply = powerSupply;

        }

        public string Work()
        {
            int power = _powerSupply.GetPower();
            if (power<=0)
            {
                return "Doesn't work.";
            }else if (power<100)
            {
                return "slow";
            }else if (power < 200 && power >=100)
            {
                return "work well";
            }
            else
            {
                return "warning!";
            }
        }
    }
}

 

例:单元测试

被测对象:需要测试高于200和等于0的情况

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

namespace ConsoleApp54
{
    class Program
    {
        static void Main(string[] args)
        {
            var fan = new DeskFan(new PowerSupply());
            Console.WriteLine(fan.Work());
        }
    }
   public interface IPowerSupply
    {
        int GetPower();
    }

   public class PowerSupply:IPowerSupply
    {
        public int GetPower()
        {
            return 100;                                                                       
        }
    }

   public class DeskFan 
    {
        private IPowerSupply _powerSupply;//接口解耦
        public DeskFan(IPowerSupply powerSupply)
        {
            _powerSupply = powerSupply;

        }

        public string Work()
        {
            int power = _powerSupply.GetPower();
            if (power<=0)
            {
                return "Doesn't work.";
            }else if (power<100)
            {
                return "slow";
            }else if (power < 200 && power >=100)
            {
                return "work well";
            }
            else
            {
                return "warning!";
            }
        }
    }
}

下面是单元测试:在解决方案右键→添加→新建项目→测试,根据框架选择合适的项目→生成测试页面如下→在‘’测试‘’选项中选择窗口,再选择测试资源管理器,在弹出的窗口中调试代码

using System;
using ConsoleApp54;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace UnitTestProject1
{
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void PowerLowerThanZero_OK()
        {
            var fan = new DeskFan(new PowerSupplyLowerThanZero());
            var expected = "Doesn't work.";
            var actual= fan.Work();
            Assert.AreEqual(expected, actual);//Assert用于单元测试的类,不满足被测条件则引发各种异常
        }
        [TestMethod]
        public void PowerMoreThan200_OK()
        {
            var fan=new DeskFan(new PowerExplode());
            var expected = "warning!";
            var actual = fan.Work();
            Assert.AreEqual(expected, actual);
        }
    }

    class PowerSupplyLowerThanZero : IPowerSupply
    {
        public int GetPower()
        {
            return 0;
        }
    }

    class PowerExplode : IPowerSupply
    {
        public int GetPower()
        {
            return 1000;
        }
    }
}

思考:

这样可以调试,但是不够美,每次都要写很多类,有没有什么办法呢?

答:有,在测试截面右键鼠标→选择管理NuGet程序包→搜索Moq,下载安装→在名称空间引用Moq

using System;
using ConsoleApp54;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

namespace UnitTestProject1
{
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void PowerLowerThanZero_OK()
        {
            var mock = new Mock<IPowerSupply>();
            mock.Setup(ps => ps.GetPower()).Returns(() => 0);
            var fan = new DeskFan(mock.Object);
            var expected = "Doesn't work.";
            var actual = fan.Work();
            Assert.AreEqual(expected, actual);//Assert用于单元测试的类,不满足被测条件则引发各种异常
        }
        [TestMethod]
        public void PowerMoreThan200_OK()
        {
            var mock = new Mock<IPowerSupply>();
            mock.Setup(ps => ps.GetPower()).Returns(() => 1000);
            var fan = new DeskFan(mock.Object);
            var expected = "warning!";
            var actual = fan.Work();
            Assert.AreEqual(expected, actual);
        }
    }
}

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值