C#学习(13)接口,抽象类,SOLID,单元测试,反射

烈酒焚心 提交于 2020-03-07 06:51:38

什么是接口和抽象类

  • 接口和抽象类都是“软件工业产物”
  • 具体类→抽象类→接口:越来越抽象,内部实现的东西越来越少
  • 抽象类是未完全实现逻辑的类(可以有字段和非public成员,它们代表了“具体逻辑”),不可被实例化
  • 抽象类为复用而生:专门作为基类来使用,也具有解耦功能
  • 封装确定的,开放不确定的,推迟到合适的子类中去实现
  • 接口是完全未实现逻辑的“类”(“纯虚类”;只有函数成员;成员全部public)
  • 接口为解耦而生:“高内聚,低耦合”,方便单元测试
  • 接口是一个“协约”,早已为工业生产所熟知(有分工必有协作,有协作必有协约)
  • 他们都不能实例化,只能用来声明变量,或引用具体类的实例

为做基类而生的“抽象类”与“开放/关闭原则”

抽象类:

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

    abstract class Vehicle
    {
        public abstract void Run();

        public void Fill()
        {
            Console.WriteLine("Pay and fill");
        }
        
        public void Stop()
        {
            Console.WriteLine("Stopped");
        }
    }

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

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

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

接口:类中全为抽象成员,成员必须为public

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

    interface IVehicle
    {
        void Stop();
        void Fill();
        void Run();
    }
    //接口相当于纯虚类,类中的成员都是抽象
    //abstract class IVehicle
    //{
    //    abstract public void Stop();
    //    abstract public void Fill();
    //    abstract public void Run();
    //}

    abstract class Vehicle:IVehicle
    {
        public void Fill()//若继承接口 将override去掉 并将继承所有成员 不能少
        {
            Console.WriteLine("Pay and fill");
        }
        
        public void Stop()
        {
            Console.WriteLine("Stopped");
        }

        abstract public void Run();
    }

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

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

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

接口与单元测试

  • 接口的产生:自底向上(重构),自顶向下(设计)
  • C#中接口的实现(隐式,显式,多接口)
  • 语言对面向对象的设计的内建支持:依赖反转,接口隔离,开/闭原则等
    在这里插入图片描述
namespace InterFaceExample
{
    
    class Program
    {
        static void Main(string[] args)
        {
            int[] num1 = new int[] { 1, 2, 3, 4, 5 };
            ArrayList num2 = new ArrayList { 1, 2, 3, 4, 5 };
            Console.WriteLine(Sum(num1));
            Console.WriteLine(Sum(num2));
            Console.WriteLine(Avg(num1));
            Console.WriteLine(Avg(num2));
        }

        //int所在的Array类和Arraylist类都继承自IEnumerable接口
        //免去了输入类型不同的麻烦

        static int Sum(IEnumerable nums)
        {
            int sum = 0;
            foreach (var n in nums)
            {
                sum += (int)n;
            }
            return sum;
        }

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

*紧耦合实例(Car类紧依赖Engine类)

namespace InterFaceExample
{
    
    class Program
    {
        static void Main(string[] args)
        {
            Engine engine = new Engine();
            Car car = new Car(engine);
            car.run(5);
            Console.WriteLine(car.Speed);
        }
    }

    class Engine
    {
        public int RPM { get;private set; }
        public void Work(int gas)
        {
            this.RPM = 1000 * gas;
        }
    }

    class Car
    {
        private Engine _engine;
        public Car(Engine engine)
        {
            this._engine = engine;
        }

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

*低耦合示例:

namespace InterFaceExample
{
    
    class Program
    {
        static void Main(string[] args)
        {
            //使用接口只需更换类名即可 new PhoneUser(new EricssonPhone());
            var v = new PhoneUser(new NokiaPhone());
            v.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();
    }

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

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

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

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

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

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

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

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

依赖反转,测试单元

在这里插入图片描述

namespace InterFaceExample
{
    
   public 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 "Won't work!";
            }
            else if (power < 100)
            {
                return "Slow!";
            }else if (power < 200)
            {
                return "Work fine!";
            }
            else
            {
                return "Warning";
            }
        }
    }
}

*依赖反转在测试单元中的应用:

namespace InterfaceExample.Tests
{
    public class DeskfanTest
    {
        [Fact]//测试case
        public void PowerLowerThanZero ()
        {
            var fan = new DeskFan(new PowerSupplyLowerThanZero());
            var expected = "Won't work!";
            var actual = fan.Work();
            Assert.Equal(expected, actual);
        }
    }

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

*使用Moq进行单元测试:

namespace InterfaceExample.Tests
{
    public class DeskfanTest
    {
        [Fact]//测试case
        public void PowerLowerThanZero ()
        {
            var mock = new Mock<IPowerSupply>();
            mock.Setup(ps => ps.GetPower()).Returns(() => 0);
            var fan = new DeskFan(mock.Object);
            var expected = "Won't work!";
            var actual = fan.Work();
            Assert.Equal(expected, actual);
        }
    }
}

接口隔离

接口隔离原则示例1:
Diver类只关注Run()方法,不关注Fire()方法

namespace ISPExample
{
    class Program
    {
        static void Main(string[] args)
        {
            Driver driver = new Driver(new HeavyTank());
            driver.Drive();
        }
    }

    class Driver
    {
        private IVehicle _vehicle;
        public Driver(IVehicle vehicle)
        {
            _vehicle = vehicle;
        }

        public void Drive()
        {
            _vehicle.Run();
        }
    }

    interface IVehicle
    {
        void Run();
    }

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

    class Tunck:IVehicle
    {
        public void Run()
        {
            Console.WriteLine("Tunck is running");
        }
    }

    interface IWeapon
    {
        void Fire();
    }

    interface ITank:IWeapon,IVehicle
    {

    }

    class LightTank : ITank
    {
        public void Fire()
        {
            Console.WriteLine("BOOM!");
        }

        public void Run()
        {
            Console.WriteLine("KaKaKa!");
        }
    }

    class MediumTank : ITank
    {
        public void Fire()
        {
            Console.WriteLine("BOOM!!!");
        }

        public void Run()
        {
            Console.WriteLine("KaKaKa!!!");
        }
    }

    class HeavyTank : ITank
    {
        public void Fire()
        {
            Console.WriteLine("BOOM!!!!!!");
        }

        public void Run()
        {
            Console.WriteLine("KaKaKa!!!!!!");
        }
    }

}

接口隔离原则示例2:

namespace ISPExample
{
    class Program
    {
        static void Main(string[] args)
        {
            int[] num1 = { 1, 2, 3, 4, 5 };
            ArrayList num2 = new ArrayList { 1, 2, 3, 4, 5 };
            var num3 = new ReadOnlyCollection(num1);

            Console.WriteLine(Sum(num1));
            Console.WriteLine(Sum(num2));
            Console.WriteLine(Sum(num3));
        }

        //若使用ICollection(:IEnumerable)接口  则传入的太胖 只用得着迭代,调用者绝不多要
        static int Sum(IEnumerable nums)  
        {
            int sum = 0;
            foreach (var n in nums)
            {
                sum += (int)n;
            }
            return sum;
        }
    }

    class ReadOnlyCollection : IEnumerable//自定义迭代器  继承Ienumerable接口
    {
        private int[] _array;

        public ReadOnlyCollection(int[] array)
        {
            _array = array;
        }

        public IEnumerator GetEnumerator()
        {
            return new Enumerator(this);
        }

        public class Enumerator : IEnumerator
        {
            private ReadOnlyCollection _collection;
            private int _head;

            public Enumerator(ReadOnlyCollection collection)
            {
                _collection = collection;
                _head = -1;
            }

            public object Current
            {
                get
                {
                    object o = _collection._array[_head];
                    return o;
                }
            }

            public bool MoveNext()
            {
                if (++_head < _collection._array.Length)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }

            public void Reset()
            {
                _head = -1;
            }
        }
    }

}

接口隔离原则示例2:
接口的显示实现;只有把WarmKiller类的实例当作IKiller类型的实例来用的时候,kill()方法才能被调用

namespace ISPExample
{
    class Program
    {
        static void Main(string[] args)
        {
            IKiller killer = new WarmKiller();
            killer.kill();
            var wk = killer as WarmKiller;
            wk.love();

        }
    }

    interface IGentleman
    {
        void love();
    }

    interface IKiller
    {
        void kill();
    }

    class WarmKiller : IGentleman, IKiller
    {
        public void love()
        {
            Console.WriteLine("I will love you forever");
        }

        void IKiller.kill()
        {
            Console.WriteLine("Let me kill the enemy");
        }
    }
}

反射与依赖注入

  • 反射:以不变应万变(更松的耦合),给一个对象,不用new操作符,也不知道所给对象是什么静态类型的情况下,能创建出一个同类型的变量
  • 反射与接口的结合
  • 反射与特性的结合
  • 依赖注入:此ID非彼ID,但没有彼ID就没有此ID

*反射简单示例

using System;
using System.Reflection;

namespace ISPExample
{
    class Program
    {
        static void Main(string[] args)
        {
            ITank tank = new HeavyTank();
            var t = tank.GetType();
            object o = Activator.CreateInstance(t);
            MethodInfo FireMI = t.GetMethod("Fire");
            MethodInfo RunMI = t.GetMethod("Run");
            FireMI.Invoke(o, null);
            RunMI.Invoke(o, null);
        }
    }

    class Driver
    {
        private IVehicle _vehicle;
        public Driver(IVehicle vehicle)
        {
            _vehicle = vehicle;
        }

        public void Drive()
        {
            _vehicle.Run();
        }
    }

    interface IVehicle
    {
        void Run();
    }

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

    class Tunck : IVehicle
    {
        public void Run()
        {
            Console.WriteLine("Tunck is running");
        }
    }

    interface IWeapon
    {
        void Fire();
    }

    interface ITank : IWeapon, IVehicle
    {

    }

    class LightTank : ITank
    {
        public void Fire()
        {
            Console.WriteLine("BOOM!");
        }

        public void Run()
        {
            Console.WriteLine("KaKaKa!");
        }
    }

    class MediumTank : ITank
    {
        public void Fire()
        {
            Console.WriteLine("BOOM!!!");
        }

        public void Run()
        {
            Console.WriteLine("KaKaKa!!!");
        }
    }

    class HeavyTank : ITank
    {
        public void Fire()
        {
            Console.WriteLine("BOOM!!!!!!");
        }

        public void Run()
        {
            Console.WriteLine("KaKaKa!!!!!!");
        }
    }

}

*一般使用封装好的反射:依赖注入示例

namespace ISPExample
{
    class Program
    {
        static void Main(string[] args)
        {
            //分割线以上是一次性的注册,在程序启动时注册
            var sc = new ServiceCollection();
            sc.AddScoped(typeof(ITank), typeof(HeavyTank));//将后者注入前者
            sc.AddScoped(typeof(IVehicle), typeof(Car));
            sc.AddScoped<Driver>();
            var sp = sc.BuildServiceProvider();
            //=================================================

            //分割线以下代表在程序其他地方只要能看到ServiceProvider的地方,都可以这么用,不再用new
            ITank tank = sp.GetService<ITank>();
            tank.Fire();
            tank.Run();

            var driver = sp.GetService<Driver>();
            driver.Drive();
            //优点:在程序大量引用了ITank实例后,系统升级后若Itank对应的实现类不再是HeavyTank
            //而是LightTank只需要修改sc.AddScoped(typeof(ITank), typeof(LightTank));
        }
    }

    class Driver
    {
        private IVehicle _vehicle;
        public Driver(IVehicle vehicle)
        {
            _vehicle = vehicle;
        }

        public void Drive()
        {
            _vehicle.Run();
        }
    }

    interface IVehicle
    {
        void Run();
    }

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

    class Tunck : IVehicle
    {
        public void Run()
        {
            Console.WriteLine("Tunck is running");
        }
    }

    interface IWeapon
    {
        void Fire();
    }

    interface ITank : IWeapon, IVehicle
    {

    }

    class LightTank : ITank
    {
        public void Fire()
        {
            Console.WriteLine("BOOM!");
        }

        public void Run()
        {
            Console.WriteLine("KaKaKa!");
        }
    }

    class MediumTank : ITank
    {
        public void Fire()
        {
            Console.WriteLine("BOOM!!!");
        }

        public void Run()
        {
            Console.WriteLine("KaKaKa!!!");
        }
    }

    class HeavyTank : ITank
    {
        public void Fire()
        {
            Console.WriteLine("BOOM!!!!!!");
        }

        public void Run()
        {
            Console.WriteLine("KaKaKa!!!!!!");
        }
    }

}

*用反射追求更松的耦合:“婴儿车”示例

namespace BabyStoller.App
{
    class Program
    {
        static void Main(string[] args)
        {
            var folder = Path.Combine(Environment.CurrentDirectory, "Animals");
            var files = Directory.GetFiles(folder);
            var animalTypes = new List<Type>();
            foreach (var file in files)
            {
                var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(file);
                var types = assembly.GetTypes();
                foreach (var t in types)
                {
                    if (t.GetMethod("Voice") != null)
                    {
                        animalTypes.Add(t);
                    }
                }
            }

            while (true)
            {
                for (int i = 0; i < animalTypes.Count; i++)
                {
                    Console.WriteLine($"{i + 1}.{animalTypes[i].Name}");
                }

                Console.WriteLine("=============================");
                Console.WriteLine("Please choose animal:");
                int index = int.Parse(Console.ReadLine());
                if (index > animalTypes.Count || index < 1)
                {
                    Console.WriteLine("NO such an animal,Try again!");
                    continue;
                }

                Console.WriteLine("How many times?");
                int times = int.Parse(Console.ReadLine());
                var t = animalTypes[index - 1];
                var m = t.GetMethod("Voice");
                var o = Activator.CreateInstance(t);
                m.Invoke(o, new object[] { times });
            }
        }
    }
}

添加4个动物库:(cat,sheep,cow也如下)

namespace Animals.lib2
{
    public class Dog
    {
        public void Voice(int times)
        {
            for (int i = 0; i < times; i++)
            {
                Console.WriteLine("Woof!");
            }
        }
    }
}

将生成好的2个dll库放进创建好的Animals文件夹
在这里插入图片描述
运行结果:
在这里插入图片描述
创建对外SDK(IAnimal接口和UnfinishedAttribute类)

namespace BabyStroller.SDK
{
   public interface IAnimal
    {
        void Voice(int times);
    }
}
namespace BabyStroller.SDK
{
   public class UnfinishedAttribute : Attribute 
    {

    }
}

Build之后回到Animal库中Add Reference到之前的两个库中,继承IAnimal接口

namespace Animals.lib2
{
    public class Dog:IAnimal
    {
        public void Voice(int times)
        {
            for (int i = 0; i < times; i++)
            {
                Console.WriteLine("Woof!");
            }
        }
    }
}

将更新后的库重新Build,放到Animals文件夹替换之前的库,对原BabyStoller项目进行修改

namespace BabyStoller.App
{
    class Program
    {
        static void Main(string[] args)
        {
            var folder = Path.Combine(Environment.CurrentDirectory, "Animals");
            var files = Directory.GetFiles(folder);
            var animalTypes = new List<Type>();
            foreach (var file in files)
            {
                var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(file);
                var types = assembly.GetTypes();
                foreach (var t in types)
                {
                    if (t.GetInterfaces().Contains(typeof(IAnimal)))
                    {
                        var isUnfinished = t.GetCustomAttributes(false).Any(a => a.GetType() == typeof(UnfinishedAttribute));
                        if (isUnfinished) continue;
                        animalTypes.Add(t);
                    }
                }
            }

            while (true)
            {
                for (int i = 0; i < animalTypes.Count; i++)
                {
                    Console.WriteLine($"{i + 1}.{animalTypes[i].Name}");
                }

                Console.WriteLine("=============================");
                Console.WriteLine("Please choose animal:");
                int index = int.Parse(Console.ReadLine());
                if (index > animalTypes.Count || index < 1)
                {
                    Console.WriteLine("NO such an animal,Try again!");
                    continue;
                }

                Console.WriteLine("How many times?");
                int times = int.Parse(Console.ReadLine());
                var t = animalTypes[index - 1];
                var m = t.GetMethod("Voice");
                var o = Activator.CreateInstance(t);
                var a = o as IAnimal;
                a.Voice(times);
            }
        }
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!