主流的几种编程模式
面向对象编程、面向过程编程、函数式编程
面向过程编程的语言:
Basic、Pascal、C
什么是面向对象编程
面向对象编程是一种编程范式或编程风格。它以类或对象作为组织代码的基本单元,并将封装、抽象、继承、多态四个特性,作为代码设计和实现的基石 。
面向对象编程语言是支持类或对象的语法机制,并有现成的语法机制,能方便地实现面向对象编程四大特性(封装、抽象、继承、多态)的编程语言。
什么是面向过程
面向过程编程也是一种编程范式或编程风格。它以过程(可以理解为方法、函数、操作)作为组织代码的基本单元,以数据(可以理解为成员变量、属性)与方法相分离为最主要的特点。
面向过程风格是一种流程化的编程风格,通过拼接一组顺序执行的方法来操作数据完成一项功能。
面向对象编程和面向过程具有哪些优势
对于大规模复杂程序的开发,程序的处理流程并非单一的一条主线,而是错综复杂的网状结构。面向对象编程比起面向过程编程,更能应对这种复杂类型的程序开发。
面向对象编程相比面向过程编程,具有更加丰富的特性(封装、抽象、继承、多态)。利用这些特性编写出来的代码,更加易扩展、易复用、易维护。
从编程语言跟机器打交道的方式的演进规律中,我们可以总结出:面向对象编程语言比起面向过程编程语言,更加人性化、更加高级、更加智能。
面向过程适用于较小规模程序开发,快速开发 不需要考虑结构设计 结构简单
接口和抽象类的区别
接口
1.接口通过interface修饰
2.接口默认就是abstract修饰 加不加都一样
3.接口不能拥有成员变量
4.接口中的定义的变量编译之后都是static final
5.接口中的方法都是抽象的
6.java8 可以通过default关键字 设置默认实现
7.接口可以多实现
抽象类
1.抽象类通过abstract修饰
2.抽象类不能实例化
3.抽象类中可以含有抽象方法不对方法进行实现
4.抽象类只能单继承 同时子类必须对抽象方法进行实现 除非子类也是抽象方法
什么时候选择抽象类什么时候选择接口
抽象类
- 对代码的复用 同时表示的is a的关系的时候
接口
- 表示has-a(具有某种特性) 同时是为了隔离实现 解决抽象 提高代码的扩展性
如何基于原则"面向接口而非实现编程"
应用这条原则,可以将接口和实现相分离,封装不稳定的实现,暴露稳定的接口。上游系统面向接口而非实现编程,不依赖不稳定的实现细节,这样当实现发生变化的时候,上游系统的代码基本上不需要做改动,以此来降低耦合性,提高扩展性。
例子
如果我们系统中需要有个需要上传下载图片到阿里云的功能步骤如下:
1.创建 bucket(你可以简单理解为存储目录)
2.生成 access token 访问凭证、携带 access token
3.上传图片到指定的 bucket 中。
public class AliyunImageStore { //...省略属性、构造函数等... public void createBucketIfNotExisting(String bucketName) { // ...创建bucket代码逻辑... // ...失败会抛出异常.. } public String generateAccessToken() { // ...根据accesskey/secrectkey等生成access token } public String uploadToAliyun(Image image, String bucketName, String accessToken) { //...上传图片到阿里云... //...返回图片存储在阿里云上的地址(url)... } public Image downloadFromAliyun(String url, String accessToken) { //...从阿里云下载图片... } } // AliyunImageStore类的使用举例 public class ImageProcessingJob { private static final String BUCKET_NAME = "ai_images_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); } }
面向接口版本
这个需求需要更改,从阿里云改为私有云 私有云并不需要accessToken
可能会有下面2种改动
方式一
1.在原有类的名字修改改为PrivateImageStore
2.删除无用的方法将uploadToAliyun改为upload并移除accessToken参数
方式二
重新定义一个新类 然后上层代码移除 重新完整替换
面向接口编程而非实现编程原则
1.函数的命名不能暴露任何实现细节。比如,前面提到的 uploadToAliyun() 就不符合要求,应该改为去掉 aliyun 这样的字眼,改为更加抽象的命名方式,比如:upload()。
2.封装具体的实现细节。比如,跟阿里云相关的特殊上传(或下载)流程不应该暴露给调用者。我们对上传(或下载)流程进行封装,对外提供一个包裹所有上传(或下载)细节的方法,给调用者使用。
3.为实现类定义抽象的接口。具体的实现类都依赖统一的接口定义,遵从一致的上传功能协议。使用者依赖接口,而不是具体的实现类来编程
代码
public interface ImageStore { String upload(Image image, String bucketName); Image download(String url); } public class AliyunImageStore implements ImageStore { //...省略属性、构造函数等... public String upload(Image image, String bucketName) { createBucketIfNotExisting(bucketName); String accessToken = generateAccessToken(); //...上传图片到阿里云... //...返回图片在阿里云上的地址(url)... } public Image download(String url) { String accessToken = generateAccessToken(); //...从阿里云下载图片... } private void createBucketIfNotExisting(String bucketName) { // ...创建bucket... // ...失败会抛出异常.. } private String generateAccessToken() { // ...根据accesskey/secrectkey等生成access token } } // 上传下载流程改变:私有云不需要支持access token public class PrivateImageStore implements ImageStore { public String upload(Image image, String bucketName) { createBucketIfNotExisting(bucketName); //...上传图片到私有云... //...返回图片的url... } public Image download(String url) { //...从私有云下载图片... } private void createBucketIfNotExisting(String bucketName) { // ...创建bucket... // ...失败会抛出异常.. } } // ImageStore的使用举例 public class ImageProcessingJob { private static final String BUCKET_NAME = "ai_images_bucket"; //...省略其他无关代码... public void process() { Image image = ...;//处理图片,并封装为Image对象 ImageStore imageStore = new PrivateImageStore(...); imagestore.upload(image, BUCKET_NAME); } }
不要滥用此原则,需要评估改动的可能性 不然接口满天飞 导致不必要的开发负担
为何说多用组合少用继承
错误例子
如果我们设计一个关于鸟的类,大部分鸟都会飞,我们设计一个抽象的鸟类AbstractBird 有一个fly()方法 让具体的鸟类继承这个抽象类比如麻雀、鸽子、乌鸦等
需求需要增加一个鸵鸟的类 但是鸵鸟不会飞 怎么办,可以有2个选择
方案1:
public class AbstractBird { //...省略其他属性和方法... public void fly() { //... } } public class Ostrich extends AbstractBird { //鸵鸟 //...省略其他属性和方法... public void fly() { throw new UnSupportedMethodException("I can't fly.'"); } }
让不会飞的鸟重写这个飞的方法 并抛出一个异常,但是这种方案有个问题就是违反了最少知识原则或者迪米特法 对外暴露了不必要的接口 增加了调用方的错误率
方案2:
鸟类下面再定义2个类 会飞的鸟和不会飞的鸟 会飞的鸟继承会飞的鸟类 鸵鸟继承不会飞的鸟类
这样继承的层级就变深了 提高了代码的复杂性,如果我们还要区分鸟会不会叫呢 这样我们又得在会飞的鸟和不会飞的鸟下面各定义2个类 分别叫会飞的鸟会叫 会飞的鸟不会叫 不会飞的鸟会叫 不会飞的叫不会叫
看似解决了 但是如果还要区分
使用组合例子
//会飞的 public interface Flyable { void fly(); } //会叫的 public interface Tweetable { void tweet(); } //会下单的 public interface EggLayable { void layEgg(); } public class Ostrich implements Tweetable, EggLayable {//鸵鸟 //... 省略其他属性和方法... @Override public void tweet() { //... } @Override public void layEgg() { //... } } public class Sparrow impelents Flayable, Tweetable, EggLayable {//麻雀 //... 省略其他属性和方法... @Override public void fly() { //... } @Override public void tweet() { //... } @Override public void layEgg() { //... } }
这有个缺点就是 所有鸟类都要去实现一遍 并不能达到复用 使用组合加委托 分别再定义3个行为的实现类
public interface Flyable { void fly(); } public class FlyAbility implements Flyable { @Override public void fly() { //... } } //省略Tweetable/TweetAbility/EggLayable/EggLayAbility public class Ostrich implements Tweetable, EggLayable {//鸵鸟 private TweetAbility tweetAbility = new TweetAbility(); //组合 private EggLayAbility eggLayAbility = new EggLayAbility(); //组合 //... 省略其他属性和方法... @Override public void tweet() { tweetAbility.tweet(); // 委托 } @Override public void layEgg() { eggLayAbility.layEgg(); // 委托 } }
组合并不是完美的 继承也不是一无是处,仅仅是为了代码复用 并没有is-a关系的 属于滥用
在具有is-a关系 并且层级不深 结构不复杂可以大胆的使用继承