设计模式(结构型)

结构型设计模式关注如何将现有的类或对象组织在一起形成更加强大的结构。并且根据我们前面学习的合成复用原则,我们该如何尽可能地使用关联关系来代替继承关系是我们本版块需要重点学习的内容。

类/对象适配器模式

在生活中,我们经常遇到这样的一个问题:笔记本太轻薄了,以至于没有RJ45网口和USB A口(比如Macbook为了轻薄甚至全是type-c形式的雷电口)但是现在我们因为工作需要,又得使用这些接口来连接线缆,这时我们想到的第一个解决方案,就是去买一个转接口(扩展坞),扩展坞可以将type-c口转换为其他类型的接口供我们使用,实际上这就是一种适配模式。

由于我们的电脑没有这些接口,但是提供了type-c类型的接口,虽然接口类型不一样,但是同样可以做其他接口能做的事情,比如USB文件传输、有线网络连接等,所以,这个时候,我们只需要添加一个中间人来帮我们转换一下接口形态即可。包括我们常用的充电头,为什么叫电源适配器呢?我们知道传统的供电是220V交流电,但是我们的手机可能只需要5V的电压进行充电,虽然现在有电,但是不能直接充,我们也不可能让电力公司专门为我们提供一个5V的直流电使用。这时电源适配器就开始发挥作用了,比如苹果的祖传5V1A充电头,实际上就是将220V交流电转换为5V的直流电进行传输,这样就相当于在220V交流电和我们的手机之前,做了一个适配器的角色。

在我们的Java程序中,也会经常遇到这样的问题,比如:

public class TestSupplier {   //手机供应商public String doSupply(){return "iPhone 14 Pro";}
}
public class Main {public static void main(String[] args) {TestSupplier supplier = new TestSupplier();test( ? );   //我们没有Target类型的手机供应商,只有其他的,那这里该填个啥}public static void test(Target target){   //现在我们需要调用test方法,但是test方法需要Target类型的手机供应商System.out.println("成功得到:"+target.supply());}
}
public interface Target {    //现在的手机供应商,并不是test方法所需要的那种类型String supply();
}

这个时候,我们就可以使用适配器模式了,适配器模式分为类适配器和对象适配器,我们首先来看看如何使用类适配器解决这种问题,我们直接创建一个适配器类:

public class TestAdapter extends TestSupplier implements Target {  //让我们的适配器继承TestSupplier并且实现Target接口@Overridepublic String supply() {   //接着实现supply方法,直接使用TestSupplier提供的实现return super.doSupply();}
}

这样,我们就得到了一个Target类型的实现类,并且同时采用的是TestSupplier提供的实现。

public static void main(String[] args) {TestAdapter adapter = new TestAdapter();test(adapter);
}public static void test(Target target){System.out.println("成功得到:"+target.supply());
}

不过,这种实现方式需要占用一个继承坑位,如果此时Target不是接口而是抽像类的话,由于Java不支持多继承,那么就无法实现了。同时根据合成复用原则,我们应该更多的通过合成的方式去实现功能,所以我们来看看第二种,也是用的比较多的一种模式,对象适配器:

public class TestAdapter implements Target{   //现在不再继承TestSupplier,仅实现TargetTestSupplier supplier;public TestAdapter(TestSupplier supplier){this.supplier = supplier;}@Overridepublic String supply() {return supplier.doSupply();}
}

现在,我们就将对象以组合的形式存放在TestAdapter中,依然是通过存放的对象调用具体实现。

桥接模式

相信各位都去奶茶店买过奶茶,在购买奶茶的时候,店员首先会问我们,您需要什么类型的奶茶,比如我们此时点了一杯啵啵芋圆奶茶,接着店员会直接问我们需要大杯、中杯还是小杯,最后还会询问我们需要加什么配料,比如椰果、珍珠等,最后才会给我们制作奶茶。

那么现在让你来设计一下这种模式的Java类,该怎么做呢?首先我们要明确,一杯奶茶除了类型之外,还分大中小杯,甚至可能还分加什么配料,这个时候,如果我们按照接口实现的写法:

public interface Tea {   //由具体类型的奶茶实现String getType();   //不同的奶茶返回的类型不同
}
public interface Size {   //分大杯小杯中杯String getSize();
}

比如现在我们创建一个新的类型:

/*** 大杯芋圆啵啵奶茶*/
public class LargeKissTea implements Tea, Size{@Overridepublic String getSize() {return "大杯";}@Overridepublic String getType() {return "芋圆啵啵奶茶";}
}

虽然这样设计起来还挺合理的,但是如果现在我们的奶茶品种多起来了,并且每种奶茶都有大中小杯,现在一共有两个维度需要考虑,那么我们岂不是得一个一个去创建这些类?甚至如果还要考虑配料,那么光创建类就得创建不知道多少个了。显然这种设计不太好,我们得换个方式。

这时,就可以使用我们的桥接模式了,现在我们面临的问题是,维度太多,不可能各种类型各种尺寸的奶茶都去创建一个类,那么我们就还是单独对这些接口进行简单的扩展,单独对不同的维度进行控制,但是如何实现呢?我们不妨将奶茶的类型作为最基本的抽象类,然后对尺寸、配料等属性进行桥接:

public abstract class AbstractTea {protected Size size;   //尺寸作为桥接属性存放在类中protected AbstractTea(Size size){   //在构造时需要知道尺寸属性this.size = size;}public abstract String getType();   //具体类型依然是由子类决定
}

不过这个抽象类提供的方法还不全面,仅仅只有Tea的getType方法,我们还需要添加其他维度的方法,所以继续编写一个子类:

public abstract class RefinedAbstractTea extends AbstractTea{protected RefinedAbstractTea(Size size) {super(size);}public String getSize(){   //添加尺寸维度获取方式return size.getSize();}
}

现在我们只需要单独为Size创建子类即可:

public class Large implements Size{@Overridepublic String getSize() {return "大杯";}
}

现在我们如果需要一个大杯的啵啵芋圆奶茶,只需要:

public class KissTea extends RefinedAbstractTea{   //创建一个啵啵芋圆奶茶的子类protected KissTea(Size size) {   //在构造时需要指定具体的大小实现super(size);}@Overridepublic String getType() {return "啵啵芋圆奶茶";   //返回奶茶类型}
}

现在我们就将两个维度拆开,可以分别进行配置了:

public static void main(String[] args) {KissTea tea = new KissTea(new Large());System.out.println(tea.getType());System.out.println(tea.getSize());
}

通过桥接模式,使得抽象和实现可以沿着各自的维度来进行变化,不再是固定的绑定关系。

组合模式

组合模式实际上就是将多个组件进行组合,让用户可以对它们进行一致性处理。比如我们的文件夹,一个文件夹中可以有很多个子文件夹或是文件:

它就像是一个树形结构一样,有分支有叶子,而组合模式则是可以对整个树形结构上的所有节点进行递归处理,比如我们现在希望将所有文件夹中的文件的名称前面都添加一个前缀,那么就可以使用组合模式。

组合模式的示例如下,这里我们就用文件和文件夹的例子来讲解:

/*** 首先创建一个组件抽象,组件可以包含组件,组件有自己的业务方法*/
public abstract class Component {public abstract void addComponent(Component component);    //添加子组件public abstract void removeComponent(Component component);   //删除子组件public abstract Component getChild(int index);   //获取子组件public abstract void test();   //执行对应的业务方法,比如修改文件名称
}

接着我们来编写两种实现类:

public class Directory extends Component{   //目录可以包含多个文件或目录List<Component> child = new ArrayList<>();   //这里我们使用List来存放目录中的子组件@Overridepublic void addComponent(Component component) {child.add(component);}@Overridepublic void removeComponent(Component component) {child.remove(component);}@Overridepublic Component getChild(int index) {return child.get(index);}@Overridepublic void test() {child.forEach(Component::test);   //将继续调用所有子组件的test方法执行业务}
}
public class File extends Component{   //文件就相当于是树叶,无法再继续添加子组件了@Overridepublic void addComponent(Component component) {throw new UnsupportedOperationException();   //不支持这些操作了}@Overridepublic void removeComponent(Component component) {throw new UnsupportedOperationException();}@Overridepublic Component getChild(int index) {throw new UnsupportedOperationException();}@Overridepublic void test() {System.out.println("文件名称修改成功!"+this);   //具体的名称修改操作}
}

最后,我们来测试一下:

public static void main(String[] args) {Directory outer = new Directory();   //新建一个外层目录Directory inner = new Directory();   //新建一个内层目录outer.addComponent(inner);outer.addComponent(new File());   //在内层目录和外层目录都添加点文件,注意别导错包了inner.addComponent(new File());inner.addComponent(new File());outer.test();    //开始执行文件名称修改操作
}

可以看到我们对最外层目录进行操作后,会递归向下处理当前目录和子目录中所有的文件。

装饰模式

装饰模式就像其名字一样,为了对现有的类进行装饰。比如一张相片就一张纸,如果直接贴在墙上,总感觉少了点什么,但是我们给其添加一个好看的相框,就会变得非常对味。装饰模式的核心就在于不改变一个对象本身功能的基础上,给对象添加额外的行为,并且它是通过组合的形式完成的,而不是传统的继承关系。

比如我们现在有一个普通的功能类:

public abstract class Base {   //顶层抽象类,定义了一个test方法执行业务public abstract void test();
}
public class BaseImpl extends Base{@Overridepublic void test() {System.out.println("我是业务方法");   //具体的业务方法}
}

不过现在的实现类太单调了,我们来添加一点装饰上去:

public class Decorator extends Base{   //装饰者需要将装饰目标组合到类中protected Base base;public Decorator(Base base) {this.base = base;}@Overridepublic void test() {base.test();    //这里暂时还是使用目标的原本方法实现}
}
public class DecoratorImpl extends Decorator{   //装饰实现public DecoratorImpl(Base base) {super(base);}@Overridepublic void test() {    //对原本的方法进行装饰,我们可以在前后都去添加额外操作System.out.println("装饰方法:我是操作前逻辑");super.test();System.out.println("装饰方法:我是操作后逻辑");}
}

这样,我们就通过装饰模式对类的功能进行了扩展:

public static void main(String[] args) {Base base = new BaseImpl();Decorator decorator = new DecoratorImpl(base);  //将Base实现装饰一下Decorator outer = new DecoratorImpl(decorator);  //装饰者还可以嵌套decorator.test();outer.test();
}

这样我们就实现了装饰模式。

代理模式

代理模式和装饰模式很像,初学者很容易搞混,所以这里我们得紧接着来讲解一下。首先请记住,当无法直接访问某个对象或访问某个对象存在困难时,我们就可以通过一个代理对象来间接访问。

实际上代理在我们生活中处处都存在,比如手机厂商要去销售手机,但是手机厂商本身没有什么渠道可以大规模地进行售卖,很难与这些消费者进行对接,这时就得交给代理商去进行出售,比如Apple在中国的直营店很少,但是在中国的授权经销商却很多,手机厂商通过交给旗下代理商的形式来进行更大规模的出售。比如我们经常要访问Github,但是直接连接会发现很难连的上,这时我们加了一个代理就可以轻松访问,也是在体现代理的作用。

同时,代理类需要保证客户端使用的透明性,也就是说操作起来需要与原本的真实对象相同,比如我们访问Github只需要输入网址即可访问,而添加代理之后,也是使用同样的方式去访问Github,所以操作起来是一样的。包括Spring框架其实也是依靠代理模式去实现的AOP记录日志等。

比如现在有一个目标类,但是我们现在需要通过代理来使用它:

public abstract class Subject {public abstract void test();
}
public class SubjectImpl extends Subject{  //此类无法直接使用,需要我们进行代理@Overridepublic void test() {System.out.println("我是测试方法!");}
}

现在我们为其建立一个代理类:

public class Proxy extends Subject{   //为了保证和Subject操作方式一样,保证透明性,也得继承Subject target;   //被代理的对象(甚至可以多重代理)public Proxy(Subject subject){this.target = subject;}@Overridepublic void test() {   //由代理去执行被代理对象的方法,并且我们还可以在前后添油加醋System.out.println("代理前绕方法");target.test();System.out.println("代理后绕方法");}
}

乍一看,这不跟之前的装饰模式一模一样吗?

对装饰器模式来说,装饰者和被装饰者都实现同一个接口/抽象类。对代理模式来说,代理类和被代理的类都实现同一个接口/抽象类,在结构上确实没有啥区别。但是他们的作用不同,装饰器模式强调的是增强自身,在被装饰之后你能够在被增强的类上使用增强后的功能,增强后你还是你,只不过被强化了而已;代理模式强调要让别人帮你去做事情,以及添加一些本身与你业务没有太多关系的事情(记录日志、设置缓存等)重点在于让别人帮你做。

装饰模式和代理模式的不同之处在于思想。

当然实现代理模式除了我们上面所说的这种方式之外,我们还可以使用JDK为我们提供的动态代理机制,我们不再需要手动编写继承关系创建代理类,它能够在运行时通过反射机制为我们自动生成代理类:

public interface Subject {  //JDK提供的动态代理只支持接口void test();
}
public class SubjectImpl implements Subject{@Overridepublic void test() {System.out.println("我是测试方法!");}
}

接着我们需要创建一个动态代理的处理逻辑:

public class TestProxy implements InvocationHandler {    //代理类,需要实现InvocationHandler接口private final Object object;   //这里需要保存一下被代理的对象,下面需要用到public TestProxy(Object object) {this.object = object;}@Override   //此方法就是调用代理对象的对应方法时会进入,这里我们就需要编写如何进行代理了public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//method就是调用的代理对象的哪一个方法,args是实参数组System.out.println("代理的对象:"+proxy.getClass());   //proxy就是生成的代理对象了,我们看看是什么类型的Object res = method.invoke(object, args);   //在代理中调用被代理对象原本的方法,因为你是代理,还是得执行一下别人的业务,当然也可以不执行,但是这样就失去代理的意义了,注意要用上面的objectSystem.out.println("方法调用完成,返回值为:"+res);   //看看返回值是什么return res;   //返回返回值}
}

最后我们来看看如何创建一个代理类:

public static void main(String[] args) {SubjectImpl subject = new SubjectImpl();   //被代理的大冤种InvocationHandler handler = new TestProxy(subject);Subject proxy = (Subject) Proxy.newProxyInstance(subject.getClass().getClassLoader(),    //需要传入被代理的类的类加载器subject.getClass().getInterfaces(),    //需要传入被代理的类的接口列表handler);    //最后传入我们实现的代理处理逻辑实现类proxy.test();    //比如现在我们调用代理类的test方法,那么就会进入到我们上面TestProxy中invoke方法,走我们的代理逻辑
}

运行一次,可以看到调用代理类的方法,最终会走到我们的invoke方法中进行:

根据接口,代理对象是com.sun.proxy.$Proxy0类(看名字就知道不对劲),这个类是动态生成的,我们也找不到具体的源代码。

不过JDK提供的动态代理只能使用接口,如果换成我们一开始的抽象类,就没办法了,这时我们可以使用一些第三方框架来实现更多方式的动态代理,比如Spring都在使用的CGLib框架,Maven依赖如下:

<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.1</version>
</dependency>

由于CGlib底层使用ASM框架(JVM篇视频教程有介绍)进行字节码编辑,所以能够实现不仅仅局限于对接口的代理:

public class TestProxy implements MethodInterceptor {  //首先还是编写我们的代理逻辑private final Object target;   //这些和之前JDK动态代理写法是一样的public TestProxy(Object target) {this.target = target;}@Override   //我们也是需要在这里去编写我们的代理逻辑public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {System.out.println("现在是由CGLib进行代理操作!"+o.getClass());return method.invoke(target, objects);   //也是直接调用代理对象的方法即可}
}

接着我们来创建一下代理类:

public static void main(String[] args) {SubjectImpl subject = new SubjectImpl();Enhancer enhancer = new Enhancer();   //增强器,一会就需要依靠增强器来为我们生成动态代理对象enhancer.setSuperclass(SubjectImpl.class);    //直接选择我们需要代理的类型,直接不需要接口或是抽象类,SuperClass作为代理类的父类存在,这样我们就可以按照指定类型的方式去操作代理类了enhancer.setCallback(new TestProxy(subject));  //设定我们刚刚编写好的代理逻辑SubjectImpl proxy = (SubjectImpl) enhancer.create();   //直接创建代理类proxy.test();   //调用代理类的test方法
}

可以看到,效果其实是差不多的:

可以看到代理类是包名.SubjectImpl$$EnhancerByCGLIB$$47f6ed3a,也是动态生成的一个类,所以我们无法去查看源码,不过此类是继承自我们指定的类型的。

外观模式

你是否经历过类似的情况:今年计算机学院的奖学金评定工作开始了,由于你去年一不小心拿了个ACM的区域赛金牌,觉得自己又行了,于是也想参与到奖学金的争夺中,首先你的辅导员会通知你去打印你的获奖材料,然后你高高兴兴拿给辅导员之后,辅导员又给了你一张表,让你打印了之后填写一下,包括你的个人信息还有一些个人介绍,完成后,你本以为可以坐等发奖了,结果辅导员又跟你说我们评定还要去某某地方盖章,盖完章还要去找谁谁谁签字,最后还要参加一下答辩… 看着如此复杂的流程,你瞬间不想搞了。

实际上我们生活中很多时候都是这样,可能在办一件事情的时候,由于部门职能的不同,你得各个部门到处跑,你肯定会抱怨一句,就不能有个人来统一一下吗,就不能在一个地方一起把事情都办了吗?这时,我们就可以用到外观模式了。

外观模式充分体现了迪米特法则。可能我们的整个项目有很多个子系统,但是我们可以在这些子系统的上面加一个门面(Facade)当我们外部需要与各个子系统交互时,无需再去直接使用各个子系统,而是与门面进行交互,再由门面与后面的各个子系统操作,这样,我们以后需要办什么事情,就统一找门面就行了。这样的好处是,首先肯定方便了代码的编写,统一找门面就行,不需要去详细了解子系统,并且,当子系统需要修改时,也只需要修改门面中的逻辑,不需要大面积的变动,遵循迪米特法则尽可能少的交互。

比如现在我们设计了三个子系统,分别是排队、结婚、领证,正常情况下我们是需要分别去找这三个部门去完成的,但是现在我们通过门面统一来完成:

public class SubSystemA {public void test1(){System.out.println("排队");}
}
public class SubSystemB {public void test2(){System.out.println("结婚");}
}
public class SubSystemC {public void test3(){System.out.println("领证");}
}

现在三个系统太复杂了,我们添加一个门面:

public class Facade {SubSystemA a = new SubSystemA();SubSystemB b = new SubSystemB();SubSystemC c = new SubSystemC();public void marry(){   //红白喜事一条龙服务a.test1();b.test2();c.test3();}
}

现在我们只需要一个门面就能直接把事情办完了:

public static void main(String[] args) {Facade facade = new Facade();facade.marry();
}

通过使用外观模式,我们就大大降低了类与类直接的关联程度,并且简化了流程。

享元模式

最后我们来看看享元模式(Flyweight),那么这个"享元"代表什么意思呢?我们先来看看下面的问题:

public static void main(String[] args) {String str1 = "abcdefg";String str2 = "abcd";
}

我们发现上面的例子中,两个字符串虽然长短不同,但是却包含了一段相同的部分,那么现在我们如果要对内存进行优化:

public static void main(String[] args) {String str1 = "efg";   //由于str1包含str2,所以我们可以去掉重复的部分,当需要原本的str1时,再合在一起String str2 = "abcd";System.out.println("str1 = "+str2+str1);
}

而享元模式就是这个思想,我们可以将那些重复出现的内容作为共享部分取出,这样当我们拥有大量对象时,我们把其中共同的部分抽取出来,由于提取的部分是多个对象共享只有一份,那么就可以减轻内存的压力。包括我们的围棋,实际上我们只需要知道棋盘上的各个位置是黑棋还是白棋,实际上没有毕业创建很多个棋子对象,我们只需要去复用一个黑棋和一个白棋子对象即可。

比如现在我们有两个服务,但是他们都需要使用数据库工具类来操作,实际上这个工具类没必要创建多个,我们这时就可以使用享元模式,让数据库工具类作为享元类,通过享元工厂来提供一个共享的数据库工具类:

public class DBUtil {public void selectDB(){System.out.println("我是数据库操作...");}
}
public class DBUtilFactory {private static final DBUtil UTIL = new DBUtil();   //享元对象被存放在工厂中public static DBUtil getFlyweight(){   //获取享元对象return UTIL;}
}

最后当我们需要使用享元对象时,直接找享元工厂要就行了:

public class UserService {   //用户服务public void service(){DBUtil util = DBUtilFactory.getFlyweight();   //通过享元工厂拿到DBUtil对象util.selectDB();    //该干嘛干嘛}
}

当然,这只是简单的享元模式实现,实际上我们一开始举例的String类,也在使用享元模式进行优化,比如下面的代码:

public static void main(String[] args) {String str1 = "abcd";String str2 = "abcd";String str3 = "ab" + "cd";System.out.println(str1 == str2);System.out.println(str1 == str3);   //猜猜这三个对象是不是都是同一个?
}

虽然我们这里定义了三个字符串,但是我们发现,这三个对象指向的都是同一个对象,这是为什么呢?实际上这正是Java语言实现了数据的共享,想要了解具体实现请前往JVM篇视频教程。

Java设计模式(三)结构型 设计模式相关推荐

  1. 软件设计实验三 结构型设计模式实验

    一.实验目的 1.结合实例,熟练绘制常见的结构型设计模式结构图. 2.结合实例,熟练使用任意一种面向对象编程语言实现常见的结构型设计模式. 3.通过本实验,理解每一种结构型设计模式的模式动机,掌握模式 ...

  2. java设计模式之结构型设计模式

    结构型设计模式: 适配器(Adapter): 用途:将一个类的接口转换成客户希望的另外一个接口.Adapter模式使得原本由于接口不兼容而不能一起工作的类可以一起工作. 场景: 你想使用一个已经存在的 ...

  3. 装饰者设计模式(结构型设计模式)

    一.装饰者设计模式简介 装饰器设计模式(Decorator Pattern) 也叫包装设计模式,属于结构型模式,它是作为现有的类的⼀个包装,允许向⼀个现有的对象添加新的功能, 同时⼜不改变其结构给对象 ...

  4. 《C#设计模式》--03.代理设计模式(结构型设计模式)

    文章目录 一.基本概念 二.代码示例 普通代理 动态代理 DispatchProxy基本使用 DispatchProxy总结 提示:以下是本篇文章正文内容,下面案例可供参考 一.基本概念 1)代理设计 ...

  5. 《精通python设计模式》读书笔记之——结构型设计模式

    结构型设计模式: 结构型设计模式处理一个系统中不同实体(比如,类和对象)之间的关系,关注的是提供一种简单的对象组合方式来创造新功能.可用于实现不兼容软件之间的接口兼容. ①.适配器模式 简介: 适配器 ...

  6. 设计模式--简化解释(二)——结构型设计模式

    1.创建型设计模式 2.结构型设计模式 3.行为型设计模式 结构型设计模式 简而言之 结构模式主要涉及对象的组成,或者是实体如何相互使用.或者,另一个解释是,他们帮助回答"如何构建一个软件组 ...

  7. 结构型设计模式(五) 之 享元模式是池技术的表现

    1 定义 享元模式(Flyweight Pattern)属于结构型设计模式之一,它主要是使用共享对象有效地避免了创建过多的性质相近的对象,从而降低内存的占用,提高程序的性能.它也是池技术的重要实现方式 ...

  8. Java设计模式之结构型:享元模式

    一.什么是享元模式: 享元模式通过共享技术有效地支持细粒度.状态变化小的对象复用,当系统中存在有多个相同的对象,那么只共享一份,不必每个都去实例化一个对象,极大地减少系统中对象的数量.比如说一个文本系 ...

  9. Java设计模式之结构型:组合模式

    前言: 我们对于上面两幅图片肯定非常熟悉,这两幅图片我们都可以看做是一个文件结构,对于这样的结构我们称之为树形结构.在数据结构中我们知道可以通过调用某个方法来遍历整个树,当我们找到某个叶子节点后,就可 ...

最新文章

  1. Android ANR 分析
  2. Activity和Task
  3. 精算模型1 一元生存分析2 参数生存模型
  4. windows下安装nginx
  5. 让人期待的2011年度最佳 jQuery 插件发布啦
  6. SAP UI5 Routing 路由介绍
  7. python和c 先学哪个-C和Python我该先学什么?
  8. linux基础学习之关机命令总结
  9. [Asp.net Mvc]通过UrlHelper扩展为js,css静态文件添加版本号
  10. matlab 求解发动机换算转速,换算转速(发动机换算转速定义)
  11. Tools - UML绘图工具
  12. Win10登陆界面卡住,进去后无法打开网络相关的设置,谷歌浏览器无法上网
  13. JavaScript的ES3, ES5, ES6实现继承方式
  14. Please, configure Web Facet first!的解决办法
  15. 计算机常用英语1600,中考英语必备1600个词汇PDF.pdf
  16. 支付宝 二维码/账号/转账码/生成方式/生成不可修改金额码
  17. 欠债还钱,天经地义: 开发团队如何还技术债?
  18. 英飞凌芯片支持计划第二阶段申请计划(硅麦芯片已开启)
  19. PPT演示文稿放映时会议记录的技巧
  20. Ukulele 那些花儿

热门文章

  1. mysql 社区版密码如何修改_mysql修改密码
  2. 将10000张妹子图片存起来,很棒
  3. 我的世界python俄罗斯方块_300行Python代码实现俄罗斯方块,致敬逝去的童年
  4. 高德地图多边形地图的设置
  5. 十 种 男 朋 友 早 断 为 好
  6. “api工厂”如何对接达达配送
  7. lisp捕捉垂足_Autolisp对象捕捉设置
  8. 搜索邻近计算机打印机,使用组策略设置控制打印机 - Windows Server | Microsoft Docs...
  9. 新余电子计算机学校96文秘班,新余职教中心
  10. 大砍广告投放,还威胁要下架Twitter,马斯克怒向苹果开炮:宁可开战也不付30%“过路费”...