设计模式(2)

2023-11-20

2.2 结构型模式

结构型模式一共有七种。其中,适配器模式和装饰模式统称为包装模式,装饰模式和代理模式的类图基本相同,但目的不同。这些有相似目的或者有相似结构的模式需要对其概念辨析清楚,才能较好地掌握。下面将对结构型模式分别进行介绍。

2.2.1 适配器模式

模式原理

适配器模式把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法一起工作的两个类能够一起工作。说白了就是功能提供者和功能使用者无法匹配,需要一个适配器角色进行功能转换。比如手机充电需要30V电压,家用插座提供220V电压,两者之间就需要充电器充当适配器角色,来将220V电压转换成30V才能正常充电。

在适配器模式中有三种角色,一种是目标接口target,一种是源类adaptee,一种是适配器adapter。目标接口对外提供功能,但实际上是通过适配器来调用源类的功能来实现的。以上面说的手机充电为例,手机具有充电功能,但该功能最终的实际提供者是插座,那手机和插座怎么连接呢?通过充电器进行连接。于是目标类即手机接口,该接口具有充电功能:

public interface IMobile {
    // 目标接口的充电功能
    void charge();
}

现在用户要求对手机进行充电:

public static void main(String[] args) {
    IMobile iphone = new IPhone();
    iphone.charge();
}

对用户(客户端)来讲,只关心目标(即手机),然后使用目标(手机)的充电功能。至于该功能具体怎么实现并不关心。而客户不关心的充电功能的实现正是适配器模式的适配体现:

public class IPhone implements IMobile {

    private Charger adapter;

    @Override
    public void charge() {
        System.out.println("我是手机,用户正在对我进行充电");
        adapter = new Charger();
        adapter.charge();
    }

    public Charger getAdapter() {
        return adapter;
    }

    public void setAdapter(Charger adapter) {
        this.adapter = adapter;
    }
}

可以看到,手机(目标)的充电功能使用了充电器(适配器)进行充电。而充电器充电功能的实现使用了插座提供的充电功能,并对其进行适配处理(降低电压):

public class Charger {

    private int voltage;

    ElectricalSocket electricalSocket;

    public void charge() {
        System.out.println("我是充电器,我正被用来充电");
        electricalSocket = new ElectricalSocket();
        this.voltage = electricalSocket.produceVoltage() - 190;
        System.out.println("我是充电器,我使用插座进行充电,我提供的电压为:" + this.voltage + "V");
    }

    public int getVoltage() {
        return voltage;
    }

    public void setVoltage(int voltage) {
        this.voltage = voltage;
    }
}

最终充电结果:

我是手机,用户正在对我进行充电
我是充电器,我正被用来充电
我是充电器,我使用插座进行充电,我提供的电压为:30V

回过头来分析一下,目标接口(IMobile)提供了一个方法(charge),该方法无法被目标的实例(IPhone)实现(手机本身是无法充电的)。然后目标实例使用了适配器(Charger),调用了适配器的同样的方法(charge)。适配器的该方法中调用了源类(ElectricalSocket)的功能,并进行一定的适配处理(将电压降低),最终实现了目标接口的方法。一句话总结:目标接口的方法最终是靠适配器调用源类来实现的

对象适配与类适配

适配器模式分为类适配和对象适配。这是从适配器与源类的关系来划分的。从上面的代码来看,适配器和源类之间是组合关系,即适配器持有源类的引用(对象),并通过这个引用来调用需要的方法与目标进行适配,这属于对象适配。还有一种实现方式就是让适配器Charger来继承源类ElectricalSocket,相当于适配器就拥有了源类的所有方法,如果将适配器的方法看成适配后的方法,就意味着适配了整个类的全部方法,这样的适配结构属于类适配。比较而言,对象适配比类适配更灵活。

应用实例

在Java中,IO相关的类的设计就使用了适配器模式。看下面的例子:

public void test() throws Exception {
    BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("D://a.txt")));
    BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("D://b.txt")));
    StringBuilder sb = new StringBuilder();
    String str;
    while ((str = br.readLine()) != null) {
        sb.append(str);
        bw.write(sb.toString());
        sb.delete(0, sb.length());
    }
    bw.flush();
}

这个例子中,字符缓冲流对象br从D盘下的a文件读取内容。从用户的角度看,通过调用目标br的方法readLine就可以读取一行内容。但是,字符缓冲流是不能直接操作文件的,只有字节缓冲流能操作文件。因此需要一个适配器InputStreamReader,通过这个适配器调用源类FileInputStream来实现文件操作。

缺省适配器

适配器模式中有一种情况叫缺省适配。某接口定义了多个接口方法,实际可能只关心其中的一个或两个方法,其余的方法并不关注。但按照接口规则,要实现该接口的话,所有的方法都得实现,于是不关注的方法需要实现为空。这样虽然没有问题,但是写起来繁琐,可读性也差。这种情况下,可以用一个抽象类来实现该接口,在抽象类中,所有的接口方法都提供缺省的空实现。用户类继承该抽象类,选择关注的方法来实现,而不用管其他不关注的方法了。这个抽象类就是缺省适配器。

2.2.2 装饰模式

模式原理

前文说过,适配器模式和装饰模式都有一个别称叫包装模式。所谓包装,就是对源进行一定的装饰或者改变,以达到最终使用的目的。适配器模式通过适配器的适配将一个类的接口变成客户端所期待的另一种接口,而装饰模式则是源对象的功能进行某种扩展。适配器模式需要一个适配器角色来进行衔接,而装饰模式不需要。一句话总结:适配器模式的目标和源属于不同的类别;装饰模式的目标(被装饰者)和装饰者都实现了装饰接口,属于同一个类别。

装饰模式中有两种角色:装饰者和被装饰者。实际上这两种角色都是同一个类型的,实现了同一个接口。类图如下(该图引用自参考资料1,文中较好地阐述了装饰模式的作用):
这里写图片描述

举个栗子,假设被装饰者是一个礼物,然后分别用盒子和袋子进行装饰(包装),最终得到的是包裹了袋子和盒子的礼物。那么在这里栗子中,需要一个公共接口,该接口有一个描述方法,能够描述装饰的结果:

// 公共接口
public interface IComponent {
    String desc();
}

第一个角色(被装饰者)实现了这个接口,描述了装饰为零的情况下的初始目标:

public class ConcreteComponent implements IComponent {
    @Override
    public String desc() {
        return "礼物";
    }
}

第二个角色(装饰者)也实现了这个接口,并持有被装饰者的引用,描述装饰结果时直接展示被装饰者的初始描述:

public class Decorator implements IComponent {

    private IComponent component;

    public Decorator(IComponent component) {
        this.component = component;
    }

    @Override
    public String desc() {
        return component.desc();
    }
}

具体的装饰者盒子:

public class FirstDecorator extends Decorator {

    public FirstDecorator(IComponent component) {
        super(component);
    }

    @Override
    public String desc() {
        System.out.println("盒子装饰者开始进行装饰");
        return "盒(" + super.desc() + ")子";
    }

}

具体的装饰者袋子:

public class SecondDecorator extends Decorator {

    public SecondDecorator(IComponent component) {
        super(component);
    }

    @Override
    public String desc() {
        System.out.println("袋子装饰者开始进行装饰");
        return "最终得到:袋[" + super.desc() + "]子";
    }
}

客户端调用:

public class Client {
    public static void main(String[] args) {
        Decorator decorator = new SecondDecorator(new FirstDecorator(new ConcreteComponent()));
        System.out.println(decorator.desc());
    }
}

输出结果:

袋子装饰者开始进行装饰
盒子装饰者开始进行装饰
最终得到:袋[盒(礼物)子]

应用实例

这里仍然要用JAVA中IO类的设计来举例。上节的适配器模式中,举例时说过适配器模式在IO类设计中的应用。在适配器模式中,目标与被适配者(如BufferedReader和FileInputStream)并不一定是同一类,而装饰模式中,装饰者和被装饰者都必须是同一类的。如:

DataInputStream dataInputStream = new DataInputStream(new BufferedInputStream(new FileInputStream(file)));

这里FileInputStream属于被装饰者,通过BufferedInputStream进行了一层装饰,拥有了缓冲输入流的功能,然后以被装饰后的目标new BufferedInputStream(new FileInputStream(file))为被装饰者,再次通过DataInputStream进行一层装饰,拥有了数据输入流的功能。不管是装饰者还是被装饰者,都属于InputStream的实例,属于同一类。

2.2.3 代理模式

代理模式的定义:给某一个对象提供一个代理,并由代理对象控制对原对象的引用。代理模式通过代理对象访问目标对象,在目标对象实现的基础上,增强额外的功能操作,扩展目标对象的功能。

举个例子说明,假设某厂商要邀请一位明星做广告,但是明星不会亲自来洽谈商务合作,一般交由其经纪人进行处理,这时候厂商就通过明星的经纪人来达成目的。明星只需要到时候负责拍广告,其他琐碎的事则由经纪人来完成。在这个过程中,经纪人就是明星的代理。明星属于目标对象,经纪人属于代理对象。代理对象是目标对象的扩展,并调用目标对象。上文介绍装饰模式时说过,装饰模式和代理模式的类图基本一致。代理模式有一个抽象主题角色,是一个接口。然后有目标角色和代理角色实现了这个抽象主题角色。代理角色内部含有目标对象的引用,从而可以操作目标对象。代理对象拥有与目标对象相同的接口,以便在任何时刻都能代替真实对象。

代理模式分为两类,静态代理和动态代理,其中动态代理又分为JDK代理和CGLIB代理。Spring的AOP底层就是通过动态代理实现的。

静态代理

静态代理比较简单,与动态代理比,静态代理是由开发人员编写代理类,在程序运行之前就编译好。而动态代理是在程序运行的时候通过反射机制动态地创建代理对象的。

下面通过代码看静态代理的实现:
抽象主题:

public interface IUserDao {
    void save();
}

目标类:

public class UserDao implements IUserDao {
    @Override
    public void save() {
        System.out.println("保存数据");
    }
}

代理类:

public class UserDaoProxy implements IUserDao {
    private IUserDao target;
    public UserDaoProxy(IUserDao target) {
        this.target = target;
    }
    @Override
    public void save() {
        System.out.println("开始事务...");
        target.save();
        System.out.println("提交事务");
    }
}

调用:

public class Client {
    public static void main(String[] args) {
    UserDaoProxy userDaoProxy = new UserDaoProxy(new UserDao());
        userDaoProxy.save();
    }
}

静态代理的缺点是代理对象与目标对象实现一样的接口,一旦接口增加方法,目标对象与代理对象都要维护。于是更好的代理方式是动态代理。

动态代理

动态代理对象不需要实现接口,在代理的过程中通过反射动态地生成。动态代理分为两种:JDK代理和CGLIB代理。

JDK代理
JDK代理中,抽象主题接口和目标类保持不变。只是代理类不再由开发者手动编写,而是通过系统自动生成。当然,即使是系统自动生成,也要做一些工作。直接看代码示例。
代理类生成工厂ProxyFactory:

public class ProxyFactory {

    private Object target;
    public ProxyFactory(Object target) {
        this.target = target;
    }
    public Object getProxyInstance() {
        return  Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
                new InvocationHandler() {

                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("开始事务");
                        Object rsp = method.invoke(target, args);
                        System.out.println("提交事务");
                        return rsp;
                    }
                });
    }
}

该类通过getProxyInstance方法产生代理实例,实际上是利用了JDK的Proxy类的静态方法newProxyInstance来动态生成代理对象。该方法需要三个参数:

第一个参数为类加载器,描述为:the class loader to define the proxy class。该参数用来加载代理类的,与目标类加载器是同一个。

第二个参数为代理的接口,描述为:the list of interfaces for the proxy class to implement。动态生成的代理类要实现这些接口,实际上就是抽象主题。生成代理类的时候系统会让代理类实现接口的所有方法。同时代理类还会继承Proxy类,由于Java中不允许多继承,所以不能通过继承的方式代理其他类。这也是JDK代理的特点:只能代理接口。

第三个参数为调用处理器,描述为:the invocation handler to dispatch method invocations to。意思是代理方法最终被派发给调用处理器来完成,invoke方法即是实际的代理方法。

传入这三个参数之后,系统运行时Proxy.newProxyInstance方法会通过反射来动态生成代理类,至于具体如何生成的,感兴趣的可以直接看源码(参考资料2中也有比较详细的介绍),这里就不扩展了。

最后即是客户端调用:

    public static void main(String[] args) {
        IUserDao userDao = new UserDao();
        IUserDao proxy = (IUserDao)new ProxyFactory(userDao).getProxyInstance();
        System.out.println(proxy.getClass());
        proxy.save();
    }

输出:

class com.sun.proxy.$Proxy0
开始事务
保存数据
提交事务

Cglib代理
上面说到过,因为JDK代理时继承了Proxy类,因此只能代理接口。假如某个类没有接口,则不能通过JDK代理来进行动态代理,但可以用Cglib来实现代理。Spring AOP中就大量使用了cglib代理。

Cgcli代理也叫子类代理,它是在内存中构建一个子类对象从而实现目标对象功能的扩展。Cglib是一个强大的高性能代码生成包,可以在运行期扩展Java类与实现Java接口,底层是通过字节码处理框架ASM来转换字节码并生成新的类,在类的执行过程中比较高效。

Cglib不能代理final/static方法。

代码示例:
目标类没有实现接口:

public class UserDao {

    public void save() {
        System.out.println("保存数据");
    }
}

代理工厂:

public class ProxyFactory {

    private Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }

    public Object getProxyInstance() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass());
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                System.out.println("开始事务");
                Object returnValue = method.invoke(target, args);
                System.out.println("提交事务");
                return returnValue;
            }
        });
        return enhancer.create();
    }

}

这里通过代理工厂生产代理实例的过程与JDK代理是类似的。Enhancer类似Proxy,通过setSuperClass设置要代理的目标类,通过setCallBack设置回调对象。这里的回调对象类似生成JDK代理时的调用处理器InvokeHandler,回调对象的intercept方法类似调用处理器的invoke方法,最终目标类的子类代理的代理方法调用intercept方法来完成代理。

调用:

public class Client {

    public static void main(String[] args) {
        UserDao userDao = new UserDao();
        ProxyFactory proxyFactory = new ProxyFactory(userDao);
        UserDao proxy = (UserDao)proxyFactory.getProxyInstance();
        proxy.save();
    }
}

输出:

开始事务
保存数据
提交事务
代理模式与装饰模式的区别

使用装饰模式时,目标对象(被装饰者)需要以构造函数参数的形式传入到装饰器中,目标对象和装饰器之间的关系是聚合关系。而通常情况下,代理模式中目标对象不会从外部传入,也就是说,代理者完全屏蔽了目标对象。代理对象和目标对象之间是组合关系,目标对象的生命周期由代理对象来负责。但是,代理模式实际上也可以从外部传入目标对象的。如上文中的代码示例,在客户端调用过程中,目标对象都是通过构造函数参数的形式传入到ProxyFactory中去的,此时两者之间的关系也是聚合关系。

两者之间真正的不同其实是目的的不同。装饰模式是代理模式的一个特殊应用,代理模式着重对代理过程的控制,而装饰模式则是对类的功能的装饰(加强或减弱)。代理模式对目标对象有控制权,而装饰模式没有控制权。

2.2.4 桥接模式

桥接模式的定义是:将抽象和实现分离开来,从而可以保持各部分的独立性

设计模式的定义一般都比较抽象,不太好理解,不妨从具体问题入手。
假设现在需要画几何图形,如矩形、圆形等,这个很好设计,先定义一个图形接口,然后实现不同的具体图形类:矩形类和圆形类。再进一步,要求图形是有颜色的,比如红色和黄色。显然一共有四种图形:红色矩形、红色圆形、黄色矩形、黄色圆形。最简单的方式是创建四个实现类,对应具体的每种颜色的图形。继续扩展,假如图形增加了三角形,颜色增加了蓝色,则一共会有九种图形,怎么办?老方法是继续增加九个子类,对应九种图案。但是,随着图形形状和图形颜色的越来越多,继续扩展子类是不现实的,因为会越来越臃肿。

在这个例子中,画图功能实现是通过类继承的方式来做的,因为继承关系是一种强关联关系,耦合非常紧密,所以扩展起来也是非常不方便的。那么怎么把继承这种强关联关系减弱呢?那就是构建组合关系。这时候桥接模式就能出场了,桥接模式能通过对象组合的方式将抽象与实现解耦。具体的设计就是将两个维度的可变因子(如例子中的形状和颜色)分离开来,保持各自独立的扩展,然后将两者进行组合,实现需要的功能。具体代码示例如下。

抽象类:

public abstract class AbstractShape {

    IColor color;

    public AbstractShape(IColor color) {
        this.color = color;
    }

    public void setColor(IColor color) {
        this.color = color;
    }

    public IColor getColor() {
        return color;
    }

    public abstract void draw();
}

扩充抽象类:

public class CircleShape extends AbstractShape {
    public CircleShape(IColor color) {
        super(color);
    }
    @Override
    public void draw() {
        System.out.println(color.getColor() + "圆形");
    }
}

public class RectangleShape extends AbstractShape {
    public RectangleShape(IColor color) {
        super(color);
    }
    @Override
    public void draw() {
        System.out.println(color.getColor() + "三角形");
    }
}

实现类接口:

public interface IColor {
    String getColor();
}

具体实现类:

public class GreenColor implements IColor {
    private String color;
    public GreenColor(String color) {
        this.color = color;
    }
    @Override
    public String getColor() {
        return color;
    }
    public void setColor(String color) {
        this.color = color;
    }
}

调用:

public class Client {
    public static void main(String[] args) {
        IColor green = new GreenColor("绿色");
        AbstractShape circleShape = new CircleShape(green);
        circleShape.draw();

        AbstractShape rectangle = new RectangleShape(green);
        rectangle.draw();
    }
}

输出:

绿色圆形
绿色矩形

桥接模式一共有两类,四种角色,抽象部分两个角色(抽象类和扩充类),实现部分两个角色(接口和接口的实现)。

设计建模时一般将较为基础的维度作为抽象部分,而其他具体的实现的维度作为实现部分(即使实现部分有两个以上的维度,它们的角色仍然属于实现部分的角色)。抽象类持有实现接口的引用,两者之间是组合的关系(严格来说应该是聚合)。这样就将原抽象与实现的继承关系变成了抽象与实现的组合关系,降低了耦合度,提高了扩展的灵活性。

桥接模式分离了抽象接口及其实现部分,通过聚合关系将抽象与实现部分关联起来,提供了比继承更好的解决方法。但凡事都具有两面性,桥接模式的引入会增加系统的理解和设计难度,由于聚合关系建立在抽象层,要求开发者针对抽象进行设计与编程。而且使用桥接模式需要识别出系统中两个或以上独立变化的维度,因此其使用范围具有一定的局限性。

待续…


参考资料

1.https://blog.csdn.net/nugongahou110/article/details/50413668.
2.https://blog.csdn.net/goskalrie/article/details/52458773.
3.https://stackoverflow.com/questions/18618779/differences-between-proxy-and-decorator-pattern
4.https://blog.csdn.net/zhangerqing/article/details/8239539
5.http://www.cnblogs.com/chenssy/p/3317866.html
6.http://www.cnblogs.com/chenssy/p/3317866.html

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

设计模式(2) 的相关文章

  • Qt QML多线程-WorkerScript的使用

    Qt QML多线程 WorkerScript的使用 在开发过程中 常常会遇到一些需要进行耗时计算的操作 如果这些操作都放在主线程中完成 就会导致UI界面被卡死 用户体验很不好 为了解决这个问题 我们可以将这些耗时计算操作放在一个单独的线程中

随机推荐

  • java综合(六)hibernate.current_session_context_class配置

    在前面一节 spring与Hibernate整合 事务 中 总是出现不存在激活事务的问题 结果去掉
  • 使用命令启动默认程序(例如启动系统默认浏览器打开指定网址)

    文章目录 目的 基础说明 代码示例 Golang 总结 目的 通过命令调用系统默认应用程序打开对应格式的文件是比较常用的功能 这篇文章将介绍下相关内容 基础说明 Windows windows下可以使用 start 指令来启动默认程序打开对
  • 数据结构——广度优先遍历(BFS)无向连通图

    以下是数据结构中关于广度优先遍历无向连通图的操作 编程风格参考严蔚敏版数据结构 其实深度优先遍历就是二叉树的层次遍历的推广 头文件及宏 include
  • Python----利用Threading和Queue实现多线程

    用来学习Threading Queue的组合使用 实现多线程编程 实现功能 利用 ping 来检测主机是否存活 代码如下 coding utf 8 from IPy import IP from subprocess import Pope
  • 2022年 大学生工程训练比赛[物料搬运]

    本人和团结参加了2022年大学生工程训练 简称工训赛 校赛选拔 准备了几个月的时间和花费了较多的资金 由于疫情等多种情况 很遗憾未能参加湖南省省赛 过了这么久还是写个博客记录参赛准备和调试过程 目录 一 比赛要求 二 整体思路 三 硬件模块
  • 所有OLE接口

    比较有用 记录下来供查阅 常规 函数 lUnknown 目的 控制的接口协商的对象生存期 普遍存在的任何组件 而不考虑实现 QueryInterface 公开传入的接口 函数 IEnum 目的 枚举的各种类型的列表 在许多情况下 整个 OL
  • 计算机扫描的文件保存在哪,电脑教程:文件扫描后自动保存哪里去了

    科技本身 支配宇宙的自然规律是充满魅力的 因此越来越多的人开始关注科技的相关动态 近来文件扫描后自动保存哪里去了的消息也是引起了很多人的关注 那么既然现在大家都想要知道文件扫描后自动保存哪里去了 小编今天就来给大家针对文件扫描后自动保存哪里
  • 关于 运算符号 &(与运算)、

    1 与运算 在二进制中 运算规则 0 0 0 0 1 0 1 0 0 1 1 1 类比到十进制 例如 3和4 首先化成二进制 就是 011 和 100 再进行相同位上的与运算 就是 000 最后就是0 因为是 运算符号 所以返回的是int
  • Ffmpeg视频开发教程(七)——基于ffmpeg4.0生成模拟yuv数据和模拟音频数据再合成为mp4文件

    本文主要实现使用最新版的ffmpeg生成模拟yuv数据和模拟音频数据再合成为mp4文件 重要代码都是来自官方 稳定性可靠 本文程序的环境搭建参考我的第一篇FFMPEG教程 https blog csdn net zhangamxqun ar
  • ENSP—NAT综合实验

    实验要求 1 IP地址的规划和拓扑搭建 2 IP地址的配置 AR1的代码如下 r1 interface g0 0 1 r1 GigabitEthernet0 0 1 ip add 12 1 1 1 24 r1 GigabitEthernet
  • 服务器虚拟化的优势

    1 提高硬件资源使用效率 一个服务器上可以开多个虚拟机 给不同应用使用 打破一个应用一台服务器的限制 因为某具体用户使用的时间 资源有限 多个用户 应用 就可以大大提高服务器的使用效率 减少服务器数量 可以 降低购买服务器的投资 降低服务器
  • C++(四)——C++标准模板库

    文章目录 1 STL组件 Component 2 容器 Container 2 1 序列式容器 Sequence Container 2 2 关联式容器 Associative Container 2 3 无序容器 Unordered Co
  • 用matlab绘制系统函数的DTFT

    freqz函数 frequency response of digital filter 对于一个输入离散序列 输出离散序列的离散时间系统 我们可以用它的系统函数H Z 来描述这个系统 求这个系统函数的DTFT 可以得到这个系统的幅频响应和
  • logback-spring.xml中三种相对路径生成的日志文件的位置

    logback spring xml中关于路径配置的三种写法 写法1
  • 大屏图表,ECharts 从“熟练”到入门

    阅读本文 你将 了解 配置驱动 的思想 理解 Echarts 基本概念 了解 graphic 和 动画基本玩法 了解 Echarts 基底组件的封装的思路 一 不是标题党 Echarts 简历上人均 熟练 公司最近在招外包 而因为目前大屏的
  • java自动识别文件编码格式UTF-8,UTF-8无BOM,GBK

    背景 在解读properties配置文件时windows操作系统编辑过的内容上传后总是无法通过键获取文件中内容 讲过分析是文件的编码格式为UTF 8带BOM的 因此通过该程序获取文件编码格式 import java io BufferedI
  • ES6阮一峰入门教程

    地址为 https es6 ruanyifeng com
  • visual studio 一直显示正在准备解决方案

    首先重启电脑 无法解决的情况下执行以下步骤 Kill Visual Studio Open Visual Studio without loading a solution Disable AnkhSvn as Source Control
  • vue动态绑定video视频src问题解决

    做个项目 视频部分需要先后台上传 然后前端页面显示 然后就遇到了视频动态获取地址的问题 一开始想着很简单 使用v model双向绑定就行了 结果试了下并不行 后面开始度娘 尝试过很多人说的 refs解决 结果并不行 虽然浏览器中看地址确实绑
  • 设计模式(2)

    2 2 结构型模式 结构型模式一共有七种 其中 适配器模式和装饰模式统称为包装模式 装饰模式和代理模式的类图基本相同 但目的不同 这些有相似目的或者有相似结构的模式需要对其概念辨析清楚 才能较好地掌握 下面将对结构型模式分别进行介绍 2 2