zoukankan      html  css  js  c++  java
  • C#接口显示实现在实际开发中的作用

    摘要

    任何一个C#入门的程序员都知道——当一个类型在实现接口的时候,有两种方法实现:显式实现、隐式实现。而且大家也都知道,当一个类型实现的两个接口存在相同成员定义时,显示实现可以解决这种情况。

     

    但是,在一个命名比较规范的项目中,几乎不可能出现上述情况。

     

    那么,显示实现有什么具体存在的意义吗?

    本人根据这小几年的开发历经,感觉显式实现最觉的两个作用就是:

    1. 改变接口成员的使用权限
    2. 改变接口成员的出入参数

    下面本人将会对这两种作用进一一说明


    接口定义

    本篇文章将会使用一个示例来讲述这两个作用。

    我们先来看一下示例吧:

    1. 定义一个接口类型,表示一个"元件",元件拥有两个成员
      1. 元件名称,主要是用来和其它元件进行区分
      2. 使用元件,需要一个Object类型的参数,表示将元件使用在哪个目标对象上
    2. 定义一个接口类型,表示一个"元件工厂",用来生成"元件"的实例,元件工厂拥有两个成员
      1. 根据指定的元件名称,生成一个元件实例
      2. 注册一个元件,只有被注册过的元件才能够被生产

    例子还是挺简单的,我们来把这些接口类型码出来吧:

    /// <summary>
    /// 一个元件接口
    /// </summary>
    interface IComponent
    {
        /// <summary>
        /// 元件的名称,用于区别于其它元件
        /// </summary>
        String ComponentName { get; }
    
        /// <summary>
        /// 使用这个元件
        /// <param name="target">对哪个对象使用这个元件</param>
        /// </summary>
        void Use(Object target);
    }
    
    /// <summary>
    /// 一个元件工厂接口
    /// </summary>
    interface IComponentFactory
    {
        /// <summary>
        /// 创建元件
        /// </summary>
        /// <param name="componentName"></param>
        /// <returns></returns>
        IComponent CreateComponent(String componentName);
    
        /// <summary>
        /// 注册一个元件
        /// </summary>
        /// <param name="component"></param>
        void RegistComponent(IComponent component);
    }

    实现锁与钥匙的关系

    光有接口,我们是无法工作的。因此我们开发一个叫钥匙的元件,再开发一个密码锁,钥匙可以用来开密码锁,当两者的编号相同时,锁才可以被打开。我们再做一个工厂,根据锁的编号生成一把钥匙,然后就可以开锁了。

    我们先用隐式实现来做这件事

    /// <summary>
    /// 密码锁
    /// </summary>
    class PasswordLocker
    {
        /// <summary>
        /// 开锁用的密码
        /// </summary>
        public String Code { get; set; }
    }
    
    /// <summary>
    /// 钥匙
    /// </summary>
    class Key : IComponent
    {
        /// <summary>
        /// 对应的解锁密码,只有和锁的Code相同时才可以开锁
        /// </summary>
        public string Code { get; set; }
    
        public string ComponentName
        {
            get { return this.Code; }
        }
    
        public void Use(object target)
        {
            //由于入参是继承了接口的Object类型,所以必须先对入参进行类型判断
            if (target is PasswordLocker)
            {
                PasswordLocker pl = (PasswordLocker)target;
                if (pl.Code == this.Code) Console.WriteLine("打开锁了");
                else Console.WriteLine("未能把锁打开");
            }
            else
                Console.WriteLine("目前类型不是锁,暂时不能使用该元件");
    
        }
    }
    
    /// <summary>
    /// 钥匙工厂
    /// </summary>
    class KeyFactory : IComponentFactory
    {
        private Dictionary<String, IComponent> components = new Dictionary<string, IComponent>();
    
        public IComponent CreateComponent(string componentName)
        {
            return components[componentName];
        }
    
        /// <summary>
        /// 由外部创建一个元件
        /// </summary>
        /// <param name="component"></param>
        public void RegistComponent(IComponent component)
        {
            components[component.ComponentName] = component;
        }
    }

    然后我们再写一代段码使用他们:

    PasswordLocker locker = new PasswordLocker();
    locker.Code = "12345";
    
    Key k = new Key();
    k.Code = "12345";
    KeyFactory factory = new KeyFactory();
    factory.RegistComponent(k);
    factory.CreateComponent(locker.Code).Use(locker);

    功能全部OK,但是有一些小小的瑕疵:

    1. 这把钥匙从代码上看,还可以用在不是PasswordLocker的类型上,所以Use方法中还要对入参类型进行判断;
    2. 明明是钥匙工厂,但从代码上看生产出来的依然只是元件接口类型,注册的时候也是,希能够直接使用钥匙类型;
    3. 如果我的元件工厂是能够自动在实例化的时候,从数据库里注册好所有元件了,那我自然不希望调用工厂的人可以继续Regist元件。

    确实如此,当一个项目大到多人合作的时候,能够有着明确的入参与返回类型私有化不需要其它人员调用的成员,是很有必要的一件事。

    所以现在回头看刚才的那段实现代码,我们可以提出以下要求:

    1. 钥匙只能对锁进行Use
    2. 工厂从数据库中初始化所有钥匙,然后不再开放Regist方法
    3. 工厂生成出来的元件就是钥匙,而不是其它类型。

    对新要求的实现:

    我们通过对接口的显示实现,以保证接口成员不可以被显示的调用。再定义需要的public方法,调用接口成员,以达到上面三个要求:

    /// <summary>
    /// 这是一个仅仅能用于开密码锁的元件,使用了现式实现改变入参
    /// </summary>
    class PasswordLockerKey : IComponent
    {
        private string code;
    
        public PasswordLockerKey(String code)
        {
            this.code = code;
        }
    
        string IComponent.ComponentName
        {
            get { return this.code; }
        }
    
        void IComponent.Use(object target)
        {
            PasswordLocker pl = (PasswordLocker)target;
            if (pl.Code == this.code) Console.WriteLine("打开锁了");
            else Console.WriteLine("未能把锁打开");
        }
    
        /// <summary>
        /// 利用显示继承,隐藏了以Object作为入参的方法,并开放了一个仅仅以PasswordLocker作为入参的方法。改变了参数类型
        /// </summary>
        /// <param name="locker"></param>
        public void User(PasswordLocker locker)
        {
            //将自身转化为接口类型,再调用Use才可以使用显式实现的方法
            ((IComponent)this).Use(locker);
        }
    }
    
    /// <summary>
    /// 基于数据库的钥匙工厂,使用显式实现改变了接口成员的访问权限
    /// </summary>
    class DataBaseKeyFactory : IComponentFactory
    {
        private Dictionary<String, PasswordLockerKey> keys = new Dictionary<string, PasswordLockerKey>();
    
        /// <summary>
        /// 在构造函数的同时,从数据库中加载出所有钥匙
        /// </summary>
        public DataBaseKeyFactory()
        {
            IComponentFactory f = (IComponentFactory)this;
            foreach (PasswordLockerKey k in LoadKeyFromDatabase())
            {
                f.RegistComponent(k);
            }
        }
    
        /// <summary>
        /// 这是模拟的通过数据库加载钥匙的方法
        /// </summary>
        /// <returns></returns>
        protected virtual IEnumerable<PasswordLockerKey> LoadKeyFromDatabase()
        {
            return new List<PasswordLockerKey>() 
            { 
                new PasswordLockerKey("12345")
            };
        }
    
        IComponent IComponentFactory.CreateComponent(string componentName)
        {
            return keys[componentName];
        }
    
        void IComponentFactory.RegistComponent(IComponent component)
        {
            keys[component.ComponentName] = (PasswordLockerKey)component;
        }
    
        /// <summary>
        /// 这里改变了原本接口的返回类型
        /// </summary>
        /// <param name="code"></param>
        /// <returns></returns>
        public PasswordLockerKey CreateComponent(string code)
        {
            return (PasswordLockerKey)((IComponentFactory)this).CreateComponent(code);
        }
    }

    代码中模拟了一下从数据库中取数的过程。

    通过上面的方法,我们最终在使用这些类形的时候会发现,Use的入参对象只能是PasswordLocker,Factory不可以Regist成员了,Create时一定是PasswordLockerKey类型。已经基本满足上面的需要了。

     


    小结

    在实际项目中,其实这些现象依然是很少见的。往往出现在底层接口框架被搭建好后,对接口类型进行一次基础实现时,可能会遇到这些问题。

    本人写此文章,希望大家在遇到类似问题的时候,能够想到可以使用接口的显示实现来解决。

    希望能够大家带来一些提示和帮助。

     

     

     文章为作者原创,转载请注明出处http://www.cnblogs.com/ShimizuShiori/p/5468749.html ,谢谢

  • 相关阅读:
    对象序列化流使用
    字符输入流结合字符打印流实现复制文件
    python 安装一些 直接使用pip无法安装的包 的方法
    linux记不住
    SIP
    uniapp不同型号手机适配
    uniapp app头部渐变
    vue安卓苹果下载链接合并
    Typescript
    Nodejs
  • 原文地址:https://www.cnblogs.com/ShimizuShiori/p/5468749.html
Copyright © 2011-2022 走看看