面向对象设计原则
每个对象是拥有独立责任的抽象体
真正的复用是源代码不做修改,编译+测试之后就不会再修改。
设计原则
1、依赖倒置原则(DIP)
1)高层模块(稳定)的变化不应该依赖于底层模块(变化),二者都应该依赖于抽象。
2)抽象不应该依赖于实现细节,实现细节应该依赖于抽象。
举例:Shape类 <-- Line类 , 抽象不依赖具体实现。
2、开放封闭原则(OCP)
1)对拓展开发,对更改封闭
2)类模块应该是可拓展的,但是不可修改
举例:木材公司建造一批木材,现在新的需求是需要防火。 那么开放封闭原则要求你应该对现有的产品进行拓展,而不是修改。 修改:拆了重做 ; 拓展:刷上一层防火漆。
3、单一责任原则(SRP)
1)一个类应该仅有一个引起它变化的原因。
2)类的方向隐含类的责任。
4、Liskov替换原则
1)子类必须能够替换它的基类(IS-A)
2)继承表达类型抽象。
举例:子类拿到调用父类的地方上也能用。 有的人喜欢在子类重写父类的方法,并且抛出异常,这就违背了替换原则。
5、接口隔离原则(ISP)
1)接口应该小而完备(强调方法的权限修饰)
2)不应该强迫客户程序依赖于他们不用的方法。
6、优先使用对象组合,而不是类继承
0)类继承通常是“白箱复用”,组合是”黑箱复用“;
1)继承在某种程序破坏了封装性(父类给子类暴露的接口很多),子类父类耦合度高
2)对象组合只要求被组合的对象具有良好的接口定义,耦合度低。
7、封装变化点
使用封装来创建对象之间的分界层,让设计者可以在分界层的一侧进行修改而不会影响对另一侧产生不良影响。
8、针对接口编程,而不是针对实现编程
客户端无需获取对象的具体类型,只需要知道对象的接口
具体模式
一定要理解在什么时候,什么地方使用设计模式
抉择一个设计模式,方式是重构,迭代,得到设计模式
重构的技巧
- 静态 -> 动态
- 早绑定 -> 晚绑定
- 继承->组合
- 编译时 -> 依赖运行时依赖
- 紧耦合 -> 松耦合
一、组件协作模式
模板方法 Template Method
动机:该任务具有稳定的整体操作结构,但各个子步骤却有很多改变的需求,或者是在类似框架和应用的关系下,有些具体实现要比整体实现来得晚。
定义了一个算法的稳定骨架,将某些方法的加载延迟到子类中。
这样使得子类不改变(复用)一个算法的结构就能重定义该算法的某些步骤。
两个要点:1)算法的骨架是稳定的。 2)变化在于算法的实现步骤。
核心:你别调用我,我来调用你。
红色表示稳定部分,蓝色表示变化部分。
策略模式 Strategy
一款计算税收的软件,在一开始的需求只有三个国家,业务拓展之后,增加了需求。 下面这款设计方式违背了开放封闭原则,而且有很多if else 中的算法是不会被使用到的,但是都会被装载到内存。
动机(Movtivation)
- 在软件构建过程中,某些对象使用的算法可能多种多样,经常改变,如果将这些算法都编码到对象中,将会使对象变得异常复杂;而且有时候支持不使用的算法也是一个性能负担。
- 如何在运行时根据需要透明地更改对象的算法?将算法与对象本身解耦,从而避免上述问题?
策略模式的解决方案
定义一系列算法,把它们一个个封装起来,并且使他们可互相替换(变化)。使得客户端得以稳定,尽管算法是拓展的。
要点:可以在运行时方便地根据需求在各个算法之间进行切换
观察者模式 Observer
情景:在UI设计中,经常有这种需求,上传文件,通知进度。
那么代码结构大概是这样的:
UploadForm中, 1) 开启事件–上传文件 2)向事件传入一个通知引用,表示进度的展现方式。3)通知模型引用在上传事件中会被修改,那么在UploadForm就可以根据通知模型获取到进度,渲染即可。
在UploadEvent中,1)执行上传动作 2)根据上传进度, 修改通知模型。
一、创建型(6种)
1、单例模式(Singleton)
解释UML
1)私有的数据成员
2)私有的构造方法
3)公共的getUniqueInstance()方法
双重检查
public class Singleton {
/* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载 ,使用volatile放在jvm指令重排*/
private static volatile Singleton instance = null;
/* 私有构造方法,防止被实例化 */
private Singleton() {
}
/* 静态工程方法,创建实例 */
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class){
if(instance == null)
instance = new Singleton();
}
}
return instance;
}
/* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
public Object readResolve() {
return instance;
}
}
注意双重锁检查时,对象要加上volatile:
uniqueInstance = new Singleton();分为以下几步执行
1)为uniqueintsance分配内存
2)初始化
3)将uniqueInstance指向分配的内存地址
因为JVM具有指令重排,不保证指令的执行顺序。所以执行顺序可能编程 1>3>2, 当线程T1执行了1) 3),然后线程T2调用getUniqueInstance()发现 uniqueInstance 不为空(因为存在内存空间),实际上uniqueInstance并未初始化完成。 加上volatile可以防止JVM重排指令。
https://blog.csdn.net/qq_37591656/article/details/86774476
2、简单工厂
解释UML
客户端client调用SimpleFactory,对具体的product进行生产,客户端使用的是Interface的Product
举例如下:(我们举一个发送邮件和短信的例子)
在商品之间抽取共性,之后在工厂之中根据不同的信息生产不同的商品,但是可以依据共性生产出基类
首先创建邮件和短信的共同接口
public interface Sender {
public void Send();
}
实现类
public class MailSender implements Sender {
@Override
public void Send() {
System.out.println("this is mailsender!");
}
}
public class SmsSender implements Sender {
@Override
public void Send() {
System.out.println("this is sms sender!");
}
}
多个静态方法的工厂
public class SendFactory {
public static Sender produceMail(){
return new MailSender();
}
public static Sender produceSms(){
return new SmsSender();
}
}
3、工厂方法
具体的工厂concreteFactory生产具体的产品conreteProduct
工厂模式存在一个问题,类的创建依赖于工厂类,要是进行业务拓展,那么就必须修改工厂类,违背了闭包原则。因此创建工厂的接口,需要拓展业务时,增加新的工厂实现类即可。
工厂的接口
interface Provider{
public Sender produce();
}
工厂的实现类都是根据业务进行编写的,下面是短信工厂
public class SendSmsFactory implements Provider{
@Override
public Sender produce() {
return new SmsSender();
}
}
4、抽象工厂
解释UML
客户端调用AbstractFactory对象生产AbstractProductA,AbstractProductB。
具体工厂ConcreteFactory生产具体的Product。
工厂方法模式:
一个抽象产品类,可以派生出多个具体产品类。
一个抽象工厂类,可以派生出多个具体工厂类。
每个具体工厂类只能创建一个具体产品类的实例。
抽象工厂:
多个抽象产品类,每个抽象产品类可以派生出多个具体产品类。(产品是树状结构一样具有家族系特征)
一个抽象工厂类,可以派生出多个具体工厂类。
每个具体工厂类可以针对一个抽象产品,可以生产出多个具体产品类。每个具体工厂生产整个系的产品。
大部分的抽象工厂呈现出来都是这样样子的,每个具体实现类(或者接口)里面是很多工厂方法,每个工厂方法对应生产某种类型。
抽象工厂着重点在于创建出哪类产品,接口是对准了某个抽象产品的整套具体产品。例如工厂可以生产鼠标和键盘。那么抽象工厂的实现类(具体的某个工厂)都可以生产鼠标和键盘,但是工厂A生产的鼠标和键盘是罗技的(罗技1系 2系等等)系列, 工厂B生产的是微软的。
工厂方法更像流水线, 抽象工厂才像工厂。
5、建造者模式
客户端直接访问Director进行生产。
Director和Builder是聚合关系(成员变量,需要在构造方法里传入),具体的生产者ConcreteBuilder进行生产。
用户不需要知道对象建造的过程,只需**表明自己要创建的类型和内容 **
优点
- Builder是接口,因为装机任务会有很多样,比如有人要联想的机器,有人要神舟的机器。因此方便增加创建者和删除创建者
- 分步骤进行构造复杂的对象
缺点
创建的产品一般具备较多的共同点,
6、原型模式(prototype)
客户端直接调用Prototype抽象类,具体的Prototype重写clone(),返回对象。
科大讯飞笔试出现过UML图
把对象作为原型,对其进行赋值,产生一个和原对象类似的新对象。这里关心浅赋值和深赋值问题
- 浅拷贝:只是对基本数据类型做拷贝,引用类型指向的还是原来的地址
- 深拷贝:引用类型也新开辟了一份空间(通过序列化或者二进制流写到缓冲)
/* 浅复制 */
public Object clone() throws CloneNotSupportedException {
Prototype proto = (Prototype) super.clone();
return proto;
}
/* 深复制 */
public Object deepClone() throws IOException, ClassNotFoundException {
/* 写入当前对象的二进制流 */
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
/* 读出二进制流产生的新对象 */
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return ois.readObject();
}
二、行为型
1、责任链模式(Chain Of Responsibility)
Handler抽象类:1)Handler类型的successor属性(字段);2)handleRequest()方法,用于处理某种请求。
因此如何实现责任链呢?
先有不同的ConcreteHandler handler对象
构造逻辑先后关系:把下一个处理请求的handler,放到上一个handler的successor中。
每一个handler根据特点请求,进行捕捉处理,不然就放到successor去处理。
concreteHandler的代码
public class ConcreteHandler1 extends Handler {
public ConcreteHandler1(Handler successor) {
super(successor);
}
@Override
protected void handleRequest(Request request) {
if (request.getType() == RequestType.TYPE1) {
System.out.println(request.getName() + " is handle by ConcreteHandler1");
return;
}
if (successor != null) {
successor.handleRequest(request);
}
}
}
Client的代码
public class Client {
public static void main(String[] args) {
Handler handler1 = new ConcreteHandler1(null);
Handler handler2 = new ConcreteHandler2(handler1); // 构造链的关系
Request request1 = new Request(RequestType.TYPE1, "request1");
handler2.handleRequest(request1);
Request request2 = new Request(RequestType.TYPE2, "request2");
handler2.handleRequest(request2);
}
}
2、命令(Command)
二、结构模式(7种)
https://www.cnblogs.com/geek6/p/3951677.html
6、适配器模式
将某个源类的接口转换成客户端期望的另一个接口,消除接口不匹配所造成的兼容性问题。分为类的适配器、对象的适配器、接口的适配器。
6.1 类的适配器模式
设计一个新类继承Sources源类,并且实现新功能的接口,这样新类就能用到接口上。
public class Sources {
public void method1() {
System.out.println("this is ordinary method");
}
public static void main(String[] args) {
TargetImpl impl = new TargetImpl();
impl.method2();
impl.method1();
}
}
interface Targetable{
public void method1();
public void method2();
}
class TargetImpl extends Sources implements Targetable {
@Override
public void method2() {
System.out.println("this is new method");
}
}
6.2 对象的适配器
Adapter中,不继承源类,而是把源类组合进来,并且实现接口。
class TargetImpl implements Targetable {
Sources sources;
@Override
public void method1() {
sources.method1();
}
@Override
public void method2() {
System.out.println("this is new method");
}
}
6.3 接口的适配器
有时我们写了一个接口,但是它所有的实现类必须实现全部的方法,这点比较麻烦。为了解决这个问题,就创建了一个抽象类,接口的实现类只和抽象类进行打交道,抽象类再实现所有接口的方法。
抽象类
public abstract class Wrapper2 implements Sourceable{
public void method1(){}
public void method2(){}
}
6.4总结
- 类的适配器模式:当Source类要转换成满足另一个接口的类时,通过继承Source并且继承接口的Adapter类来完成.
- 对象的适配器模式:把一个对象转换成满足某个接口的对象,通过实现一个Adapter类持有该对象,并实现接口即可
- 接口的适配模式:不希望实现接口的所有方法,因此创建一个抽象类去实现所有方法(莫名觉得怪怪的)。实现类去继承这个抽象类,并重写自己想要的方法。
7、装饰模式(Decorator)
给对象添加一些新功能,Source是被装饰对象,Decorator是装饰类,他们都要实现同一个接口。
适用场景:
- 需要拓展一个类的功能
- 动态的为对象增加功能,还能动态的撤销(增加的话就调用装饰者,删除就调用Source)。
interface Sourceable{
public void method();
}
class Source implements Sourceable{
@Override
public void method() {
System.out.println("ordanary method");
}
}
public class Decorator implements Sourceable {
private Sourceable sourceable;
public Decorator(Sourceable sourceable){
this.sourceable = sourceable;
}
@Override
public void method() {
System.out.println("before method");
sourceable.method();
System.out.println("after method");
}
public static void main(String[] args) {
new Decorator(new Source()).method();
}
}
8、代理模式
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)