• 面向对象思想理论


    面向对象的四大特性

    • 封装性:信息隐藏或者数据访问保护,类通过暴露有限的访问接口,授权外部仅能通过类提供的方式(或者叫函数)来访问内部信息或者数据
    • 抽象性:如何隐藏方法的具体实现,让调用者只需要关心方法提供了哪些功能,并不需要知道这些功能是如何实现的
    • 继承:表示类之间is-a关系,代码复用
    • 多态:子类可以替换父类,在实际的代码运行过程中,调用子类的方法实现

    面向对象和面向过程的比较

    • 什么是面向过程编程与面向过程编程语言?
      1. 面向对象编程是以类为基本单元,而面向过程编程则是以过程(或方法)作为组织代码的基本单元,最重要的特点是数据和方法分离。
      2. 不支持丰富的面向对象编程特性,如继承,多态,封装。
    • 面向对象编程相比面向过程编程有哪些优势?
      1. 对于大规模复杂程序的开发,程序的处理流程并非单一的是一条主线,而是错综复杂的网状结构。面向对象编程比起面向过程编程,更能应对这种复杂类型的程序开发
      2. 面向对象编程相比面向过程编程,具有更加丰富的特性(封装、抽象、继承、多态)。利用这些特性编写出来的代码,更加易扩展、易复用、易维护。
    • 为什么说面向对象编程语言比面向过程编程语言更高级?
      1. 更接近人的思维逻辑,将事务抽象为类,再去逐步完善行为。而面向过程则更加接近底层,需要我们有计算机思维。
    • 有哪些看似是面向对象实际上是面向过程风格的代码?
      1. 滥用get,set
      2. 滥用全局变量和全局方法。只包含静态方法而不包含任何属性的utils类,是彻彻底底的面向过程的编程风格。
      3. 定义数据和方法分离的类,如mvc编程(贫血模型)
    • 在面向对象编程中,为什么容易写出面向过程风格的代码?
      1. 思维模式的不同,面向过程是一步步按照顺序来,面向对象是一种自底而上的思考方式,不是按照执行流程去分解任务,而是将任务翻译成一个个小的模块,模块之间进行交互
    • 面向过程编程和面向过程语言就真的没有用武之地了吗?
      1. 看使用场景,如计算为主,数据为辅的情况下,就可以使用面向过程

    接口和抽象类

    定义比较

    抽象类:(is-a)

    1. 抽象类不允许被实例化,只能被继承
    2. 抽象类可以包含属性和方法,且可以包含代码实现,也可以不包含代码实现,不包含代码实现的方法叫做抽象方法
    3. 子类继承抽象类,必须实现抽象类中的所有抽象方法。

    接口类:(has-a)

    1. 接口不能包含属性(也就是成员变量)
    2. 接口只能声明方法,方法不能包含代码实现
    3. 类实现接口的时候,必须实现接口中声明的所有方法

    出发点

    抽象类是多个子类代码重复,对整体重复代码抽离出来,是自下而上的。
    接口偏重于解耦,是对行为的抽象,相对于一组协议或者契约,构建接口经常是自上而下的。

    抽象类设计思路

    public class Logger {
        private String name;
        private boolean enabled;
        private Level minPermittedLevel;
    
    
        public Logger(String name, boolean enabled, Level minPermittedLevel) {
            this.name = name;
            this.enabled = enabled;
            this.minPermittedLevel = minPermittedLevel;
        }
        
        protected boolean isLoggable(Level level) {
            boolean loggable = enabled & (minPermittedLevel.intValue <= level.intValue);
            return loggable;
        }
    }
    
    
    // 文件子类
    
    class FileLogger extends Logger {
        private Writer writer;
        private String filePath;
    
        public FileLogger(String name, boolean enabled, Level minPermittedLevel, String filePath) {
            super(name, enabled, minPermittedLevel);
            this.filePath = filePath;
        }
        
        public void log(Level level, String message) {
            if (!isLoggable(level)); return;
            writer.write(message);
        }
    }
    
    // 中间间输出子类
    class MessageQueueLogger extends Logger {
        private MessageQuesueClient messageQuesueClient;
        
        public MessageQueueLogger(String name, boolean enabled, Level minPermittedLevel, MessageQuesueClient messageQuesueClient) {
            super(name, enabled, minPermittedLevel);
            this.messageQuesueClient = messageQuesueClient;
        }
    
        public void log(Level level, String message) {
            if (!isLoggable(level)); return;
            messageQuesueClient.send(message);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    固然说这样子达到了代码复用的目的,但是已经无法使用多态特性了,不能用logger去调用log方法
    还有一种,就是写一个空log(),但是相对于抽象类而言,还是脱了裤子放屁,而且定义一个空方法,其他人也不容易去理解,也有可能忘记了该方法的实现

    模拟抽象类和借口

    接口模拟

    接口就是没有定义成员变量,只有方法声明,没有方法实现

    1. 使用抽象类模拟
    class Strategy {
    	public:
    		~Strategy();
    		virtual void algorithm()=0;
    	protected:
    		Strategy(); 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    没有定义任何属性,且所有方法都声明为virtual类型,这样所有方法都没有代码实现,且继承这个抽象类的子类都必须实现这些方法,从语法特性来说,相当于一个接口。

    1. 用普通类模拟接口
    public calss MockInterface {
    	protected MockInterface() {}
    	public void funcA() {
    		throws new MethodUnSupportedException();
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    该类构造方法是protected,只可以继承。且不重写funA,会抛出MethodUnSupportedException异常,这样就强迫子类在继承这个夫类时,都要主动实现夫类的方法。

    模拟抽象类

    抽象类的概念是有属性,有方法,可以有方法没有实现

    用普通类实现抽象类

    public calss MockInterface {
    	private int value;
    	protected MockInterface() {}
    	public void funcA() {
    		throws new MethodUnSupportedException();
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    和模拟接口类似,将构造方法保护起来,只能通过继承来实现,且未实现方法需要定义抛出异常

    基于接口而非实现编程

    广泛的对于接口的定义: 一组协议或者约定,是功能提供者提供给使用者的一个功能列表

    越抽象、越顶层、越脱离具体某一实现的设计,越能提高代码的灵活性,越能应对未来的需求变化。好的代码设计,不仅能应对当下的需求,而且在将来需求发生变化的时候,仍然能够在不破坏原有代码设计的情况下灵活应对。

    图片上传为例
    现在有一个向阿里云上传图片的类,如下

    public class AliyunImageStore {
        // ... 省略属性、构造方法....
    
        public void createBucketIfNotExisting(String bucketName) {
            // 创建bucket代码逻辑。。。
            // 。。。失败抛出异常
        }
    
        public String generateAccessToken() {
            // 根据 accesskey / secretkey 等生成access token
        }
    
        public String uploadToAliyun(Image image, String bucketName, String accessToken) {
            // 上传图片到阿里云
            // 返回图片储存地址
        }
    
        public Image downloadFromAliyun(String url, String accessToken) {
            // 从阿里云下载图片返回
        }
    }
    
    
    // AliyunImageStore类使用实例
    public class ImageProcessingJob {
        private static final String BUCKET_NAME = "al_image_bucket";
        // .....
    
    
        public void process() {
            Image image = ...; // 处理图片,并封装为Image对象
            AliyunImageStore imageStore = new AliyunImageStore();
            imageStore.createBucketIfNotExisting(BUCKET_NAME);
            String accessToken = imageStore.generateAccessToken();
            imageStore.uploadToAliyun(image, BUCKET_NAME, accessToken);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    上述流程分为了三个步骤:创建bucket(存储目录),生成accessToken,携带token上传图片到指定的buckey中。整个流程看起来没有什么问题。

    现在不往阿里云上放东西了,改成私有云了,那这个类就需要进行大改,如生成token,类名,方法名等。而且我们对这个行为进行抽象,他们都具有上传和下载功能,姑可以将该方法抽象出来。

    public interface ImageStore {
        String uploadImage(Image image, String bucketName);
        Image download(String url);
    }
    
    // 阿里云存储
    public class AliyunImageStore implements ImageStore{
        // ... 省略属性、构造方法....
    
        public void createBucketIfNotExisting(String bucketName) {
            // 创建bucket代码逻辑。。。
            // 。。。失败抛出异常
        }
    
        public String generateAccessToken() {
            // 根据 accesskey / secretkey 等生成access token
        }
        
        @Override
        public String uploadImage(Image image, String bucketName) {
            createBucketIfNotExisting(bucketName);
            String token = generateAccessToken();
            // 上传图片到阿里云
            // 返回图片储存地址
        }
    
        @Override
        public Image download(String url) {
            String token = generateAccessToken();
            // 从阿里云下载图片返回
        }
    }
    
    
    // 本地云存储
    public class PrivateImageStore implements ImageStore{
    
        public void createBucketIfNotExisting(String bucketName) {
            // 创建bucket代码逻辑。。。
            // 。。。失败抛出异常
        }
        @Override
        public String uploadImage(Image image, String bucketName) {
            createBucketIfNotExisting(bucketName);
            // 上传图片到私有云
            // 返回地址
        }
    
        @Override
        public Image download(String url) {
            // 从私有云下载图片。。。
        }
    }
    
    
    // 调用
    public class ImageProcessingJob {
        private static final String BUCKET_NAME = "al_image_bucket";
        // .....
    
    
        public void process() {
            Image image = ...; // 处理图片,并封装为Image对象
            ImageStore imageStore = new AliyunImageStore();
            imageStore.uploadImage(image, BUCKET_NAME);
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69

    注意:

    1. 最好不要将类写完后反推接口,这可能导致接口定义不够抽象,依赖具体的实现
    2. 只有当系统存在不稳定的实现时,需要将其抽象出来,当系统问题,不需要每一个类都去定义接口。
    3. 定义接口时,一方面,命名要足够通用,不能包换跟具体实现相关的字眼;另一方面,于特定实现有关的方法不要定义在接口中

    上述的使用也是存在问题的

    ImageStore imageStore = new AliyunImageStore(); //更换图片存储方法不方便
    
    • 1

    解决思路:

    • 配置文件
    • 构造方法传参
    • 工厂模式,ImageStore imageStore = ImageStoreFactory.newInstance(STORE_TYPE_CONFIG);
    • 依赖注入
    • 策略模式,使用一个Context类,使用聚合持有这个接口实例饮用,其他地方都用这个context类,变动只改动这个context类

    多用组合少用继承

    为什么不推荐使用继承

    继承解决了代码复用问题,但是如果继承层次过深,过复杂,也会影响到代码的可维护性。

    例如要设计一个鸟类这样的一个抽象的事物概念,定义为一个抽象类AbstractBird。
    现在有需求:鸟会飞,会跑,会下蛋,我们是否要定义这些方法
    那不会飞,不会跑,不会下蛋的鸟也很多,那该怎么办呢?
    就拿飞举例,给不会飞的鸟抛出异常,这固然是一种方法,还有就是再通过AbstractBird派生出两个更加细分的类,会飞的鸟和不会飞的鸟,哪再加上会跑,2*2就是要定义4个了,再有具体的要求,结构就越来越复杂,而且更改父类逻辑,子类也会收到影响。

    组合相比继承有哪些优势?

    常用的三种方式:组合,接口,委托,一块解决继承的问题。
    对于会飞的特性,定义一个flyable(),去实现这个接口,将实现类放入鸟类的成员变量中去,如果要调用fly的方法,就可以通flyable的实现类进行调用

    尽管我们鼓励多用组合少用继承,但组合也并不是完美的,继承也并非一无是处。在实际的项目开发中,我们还是要根据具体的情况,来选择该用继承还是组合。如果类之间的继承结构稳定,层次比较浅,关系不复杂,我们就可以大胆地使用继承。反之,我们就尽量使用组合来替代继承。除此之外,还有一些设计模式、特殊的应用场景,会固定使用继承或者组合。

  • 相关阅读:
    HTML元素大全(2)-表单
    Discourse 的关闭主题(Close Topic )和重新开放主题
    人大女王大学金融硕士项目——在职读研提升学历看见更大的世界
    7.1.3 Selenium的用法2
    redo日志、undo日志与事务隔离性
    Java开发过程中常用Linux命令总结
    ALTERA FPGA IPCORE核之单口RAM详细教程
    STM32 Cube MX以及STM32 H750 XBH6新建工程,HAL库,LL库
    领域自适应论文(七十):Bridging Theory and Algorithm for Domain Adaptation
    计算机网络——分组延时、丢失和吞吐量
  • 原文地址:https://blog.csdn.net/weixin_45483328/article/details/128146931