转:超越设计模

转:http://www.ibm.com/developerworks/cn/java/j-lo-beyondpattern/
刘 旭进, 软件开发工程师, IBM 中国软件开发中心

 

简介: 可复用面向对象软件的基础 -- 设计模式,以其可复用的设计初衷、精巧的逻辑思维被广大面向对象程序设计所追捧。但不少程序设计者却经常将思考的问题转换为遇到了什么场景就要用什么模式。这种八股文式的思维在某种程度上严重影响了程序设计的艺术性,并固化了程序设计者的思想,违背了设计模式的初衷。在本文中,作者总结了设计模式背后的核心思想,并提出了几个关键的设计原则,例如面向接口、封装变化、依赖倒置原则、只和朋友交谈等。程序设计者只需在程序设计时遵循这些原则,便会发现原来已经在使用某些设计模式了。

引题

GOF 的设计模式推出以后,受到程序员的热烈追捧,很多程序员不亦乐乎的埋头苦读甚至背诵其 23 个设计模式,并以熟悉设计模式而自豪。然而,在实际的程序设计中,很多程序员并未能把设计模式应用到自己的场景中。原因有很多,设计模式太多以至于常常被混淆;设计模式应用场景太局限或者程序员自己意识不到应用的场景。综合各种原因,根本原因只有一个,程序员并不能透彻理解,熟练应用设计模式的核心思想。笔者认为,设计模式并不是条条框框,设计模式也不是简单的 23 种。设计模式体现的一种思想是:尽可能的复用,而实现可复用的手段无外乎笔者总结的几个设计原则而已。

彻底的忘掉 GOF 的设计模式吧,程序设计应该是一门艺术,而不是备受束缚的那些模式。

原则一:封装变化

该原则的核心思想是,在程序设计中找出应用中可能需要变化之处,把它们独立出来以便以后可以轻易的改动或者扩充,而不影响不需要变化的部分。事实上如果您回过头去重新阅读设计模式的书籍,您会发现,封装变化几乎是每个设计模式背后的精神所在。所有的模式都提供了一套方法让系统中的某部分改变不会影响其它部分。

我们举一个简单的例子,我们建立一个 Car 的基类,有两个继承类 Benz 和 BMW, 具体参见下图 1:


图 1. Car 的第一个实现
图 1. Car 的第一个实现 

相信大部分人都会这么设计,但是这个设计有什么问题呢?我们看待问题需要以发展的眼光,假如科技发展了,所有的 Car 都可以飞了,怎么办?有人说,很简单,给 Car 加一个 protected 的 fly() 方法,这样 Benz 和 BMW 就都可以飞了,继承真伟大!好,那么如果我需要建立另外一个 Car 的子类玩具车(Toycar), 我们知道玩具车可以 run, 但不能 fly 的。怎么办?还是好办,我们可以重载玩具车的 fly 方法,让他们什么都不干。那好,又一个子类来了,模型车(ModelCar)。模型车不能 run,不能 fly,好办,继续重载他们的这些方法。见下图 2:


图 2. Car 的第二个实现
图 2. Car 的第二个实现 

如果我们有更多的 Car 的子类呢?有没有觉得有点繁琐,是的,我们需要重载太多的方法了。

继承并不能帮我们解决问题

可不可以使用接口,我们可以把 fly 从超类中取出来,分别作为接口,Flyable,这样一来只有 Benz 和 BMW 才实现 Flyable 接口,ToyCar 和 modelCar 并不实现该接口。Run 也作类似处理。见下图 3:


图 3. Car 的第三个实现
图 3. Car 的第三个实现 

大家可以看到,这其实是一个很笨的办法,除去 description() 方法,我们使用继承需要重载 3 个方法,可是我们使用接口实现则需要额外定义两个接口类和5个方法。接口方法里面并不能有实现代码,而 ToyCar 的 fly 行为和 Benz 的飞行行为也可能不尽相同,那么这就意味着我们需要实现越来越多的 fly() 方法。

接口也不行!

怎么办?想一想我们的前面提到的设计原则,把变化的和不变化的分离开来,以便我们以后可以轻易的改动和扩充,而不影响其它不需要变化的部分。我们变化的部分是什么?是否可以飞行,是否可以 run,以何种方式飞行?何种方式 run ? Benz,BMW 和 ToyCar 的飞行行为和 run 行为各不相同。我们可以把这些不同的 fly 和 run 抽象出来。见如下图 4:


图 4. Car 的第四个实现
图 4. Car 的第四个实现 

看到这,也许您应该大概明白接下来应该怎么办了。是的,很简单,我们可以给 Car 类加入飞行行为和 run 行为的实例变量。而在初始化 Car 的子类时传入的具体行为进行初始化,这样每个子类就自然拥有了相应的行为。

代码参见如下:


清单 1. Car 的实现类

				
public abstract class Car 
{ protected RunBehavior runBehavior; protected FlyBehaviro flyBehavior; public abstract description (); protected performFly() { flyBehavior.fly(); } protected performRun() { runBehavior.run(); } 
} public class Benz extends Car 
{ public String description() { System.out.println(“I am Benz!”); } public Benz() { this.runBehavior = new HighSpeedRunBehavior(); this.flyBehavior = new HighFlyingBehavior(); } 
}

 

上述代码中我们实现了 Benz,如果我们要实现 ToyCar,一个不能飞,但可以跑。尝试一下,看看多简单。


清单 2. ToyCar 的实现类

				
public class ToyCar extends Car 
{ public String description() { System.out.println(“I am Toy car!”); } public Benz() { this.runBehavior = new LowSpeedRunBehavior(); this.flyBehavior = new NoFlyingBehavior(); } 
}

 

总结

策略模式定义

定义了算法族,并分别封装起来,让他们之间可以相互替换,此模式让算法的变化独立于使用算法的客户。

  1. 回过头来,看看我们前面所作的工作,第一个我要告诉您的是,恭喜您学会了策略模式,上面我们的设计的核心实现就是使用了策略模式。
  2. 继承和接口不能解决一切问题,尽量的利用组合将为您的设计带来低耦合。
  3. 尽可能的针对接口或者抽象类而不是实现去编程,试想,如果我们定义的 Car 类组合具体的行为类,也就是实现,那么它就被绑死了,我们不可能以后再改变它的行为。但这样,我们可以在运行时动态的改变 Car 子类的具体行为。这也是我们成功的关键。
  4. 最重要的,把变化的和不变化的分离出来,封装变化的部分以应对随时改变。

原则二:只和朋友交谈

继续我们的话题,假设我们有一家汽车公司,可以生产 Benz、BMW、ToyCar 和 ModelCar(姑且这么认为吧,虽然这不太符合常理),那么该如何设计我们的实现呢?很简单,参见下面代码:


清单 3. CarCompany 类

				
public class CarCompany 
{public CarCompany () {   } public Car produce(String type) { Car car = null; if(“Benz”.equals(type)) { car = new Benz(); } else if(“BMW”.equals(type)) { car = new BMW(); } else if(“ToyCar”.equals(type)) { car = new ToyCar(); } else if(“ModelCar”.equals(type)) { car = new ModelCar(); } else { } car.assembly();      // 组装car.sprayPainting(); // 喷漆car.proof();         // 校对return car; } 
} 

 

老问题,上面的代码有问题么?但从业务逻辑上讲,当然没问题。可是还是要用变化的眼光看问题,上面的代码维护起来成本很高。上面的代码要求我们无论是 Benz、BMW、ToyCar 还是 ModelCar 都不能在将来发生变化。否则,我们这段代码就有维护的成本和风险。

有没有更有效的办法,想想我们第一个设计原则:把变化的部分提出去,变化的部分是什么,显然生成 car 的那一段。我们把它提出去,参见下面代码:


清单 4. CarFactory 和 CarCompany 另一种实现

				
public class CarFactory 
{ public CarFactory () {   } public Car createCar(String type) { Car car = null; if(“Benz”.equals(type)) { car = new Benz(); } else if(“BMW”.equals(type)) { car = new BMW(); } else if(“ToyCar”.equals(type)) { car = new ToyCar(); } else if(“ModelCar”.equals(type)) { car = new ModelCar(); } else { } return car; } 
} public class CarCompany 
{ CarFactory carFactory; public CarCompany (CarFactory carFactory) {   this.carFactory = carFactory; } public Car produce(String type) { Car car = null; Car = carFactory.createCar(type); car.assembly();        // 组装car.sprayPainting();   // 喷漆car.proof();           // 校对return car; } 
}

 

很显然,我们的 CarCompany 现在只依赖 CarFactory 一个类了。有人说这么做有什么用,我们只不过把问题转移到另外一个对象 CarFactory 了,问题依然存在。但是别忘了,我们的 CarCompany 可能不止一个 produce() 方法。它可能还有 sale(), repair() 方法。这样,我们相当于是把几个问题缩小为一个问题了。

总结

  1. 这个例子虽然很简单,但是却告诉我们一条最重要的设计原则,一个类应该尽可能少的与其它类产生联系,尽可能的保持类之间的耦合度,保持类的最少知识量。
  2. 恭喜您,您学会了简单工厂模式。
  3. 外观模式也是对本原则的典型应用。具体请参见设计模式相关书籍。

原则三:依赖倒置原则(DIP)

在您的设计里面,一定要减少对具体类的依赖,尽量依赖抽象,不要依赖具体类。这就是依赖倒置原则。

听起来有点像面向接口,不针对实现编程。的确很类似,但是这里强调的是抽象。具体说来就是不要让高层组件依赖低层组件,而且不管高层低层组件,都应该依赖抽象。高层组件最多是依赖低层组件的抽象。低层的抽象和实现也只依赖于高层的抽象。

所谓高层组件是由其它低层组件定义其行为的类。高层组件是包含重要的业务模型和策略选择,低层模块则是不同业务和策略实现。

也许您不是很理解这段话的含义。不要紧,继续我们上面的例子。假设随着汽车公司规模越来越大,业务规模拓展到了亚洲和欧洲。我们希望可以针对亚洲人和欧洲人生产出不同的同一品牌的的汽车。比如同一品牌 BMW 亚洲是左驾驶座(当然除了一些特殊地区),欧洲是右驾驶座。看看下面的实现。


清单 5. DependencyCarCompany 实现

				
public class DependencyCarCompany 
{ public CarCompany () {   } public Car produce(String style, String type) { Car car = null; if(“Asia”.equals(style)) { if(“Benz”.equals(type)) { car = new AsiaBenz(); } else if(“BMW”.equals(type)) { car = new AsiaBMW(); } else if(“ToyCar”.equals(type)) { car = new AsiaToyCar(); } else if(“ModelCar”.equals(type)) { car = new AsiaModelCar(); } else { } } else if(“Europe”.equals(style)) { if(“Benz”.equals(type)) { car = new EuropeBenz(); } else if(“BMW”.equals(type)) { car = new EuropeBMW(); } else if(“ToyCar”.equals(type)) { car = new EuropeToyCar(); } else if(“ModelCar”.equals(type)) { car = new EuropeModelCar(); } else { } car.assembly();        // 组装car.sprayPainting(); // 喷漆car.proof();         // 校对return car; } } 
}

 

够简单吧!总结它们对象之间依赖的情况如图 5 所示:


图 5. CarCompany 的第一个实现
图 5. CarCompany 的第一个实现 

我们发现 CarComany 依赖的具体类有 8 个,如果任何一个类发生改变,CarCompany 都需要改变。这至少不符合我们的原则二:只和朋友交谈。应用我原则一,把变化的部分提出来。我们可以定义两个 CarCompany 的子类:AsiaCarCompany 和 EuropeCarCompany 用来生产不同样式的同一品牌的汽车。在这两个子类里面,需要做的就是生成不同品牌和样式的汽车,然后再调用超类的三个方法。这样的话我们可以把生成汽车的方法提出来。


清单 6. CarCompany 另一种实现

				
public abstract class CarCompany 
{ public Car produce(String type) { Car car = createCar(type); car.assembly();        // 组装car.sprayPainting(); // 喷漆car.proof();         // 校对return car; } protected abstract Car createCar(String type); 
} public class AsiaCarCompany 
{ protected Car createCar(Sting type) { Car car = null; if(“Benz”.equals(type)) { car = new AsiaBenz(); } else if(“BMW”.equals(type)) { car = new AsiaBMW(); } else if(“ToyCar”.equals(type)) { car = new AsiaToyCar(); } else if(“ModelCar”.equals(type)) { car = new AsiaModelCar(); } else { } return car; } 
} public class EuropeCarCompany 
{ protected Car createCar(Sting type) { Car car = null; if(“Benz”.equals(type)) { car = new EuropeBenz(); } else if(“BMW”.equals(type)) { car = new EuropeBMW(); } else if(“ToyCar”.equals(type)) { car = new EuropeToyCar(); } else if(“ModelCar”.equals(type)) { car = new EuropeModelCar(); } else { } return car; } 
}

 

DependencyCarCompany 的问题在于,它依赖于每一个具体的 Car 类型。然而,在应用第二种方法后,CarCompany 现在只依赖 Car 类型的抽象,不再依赖具体类型,而是把这些依赖转移到子类中。我们可以画一个对象依赖图 6:


图 6. CarCompany 的第二个实现
图 6. CarCompany 的第二个实现 

从这个图中我们可以看出:

  1. 我们的高层组件也就是 CarCompany 已经由原来的 8 个低层对象依赖变化为只依赖一个低层对象的抽象 Car。这就是依赖抽象。
  2. 对比上面两个图,您会发现,以前所绘制的依赖是自上而下,而现在则是倒置过来。高层和低层组件都依赖于抽象的 Car。这就是依赖的倒置。

总结

工厂方法模式定义

定义一个创建对象的接口,但由子类决定要实例化哪个类。工厂方法让类的实例化推迟到的子类。

  1. 恭喜您,您学会了工厂方法模式。上面的例子实际上是工厂方法的一个典型应用。
  2. 一些辅助原则可以帮助您更好的运用 DIP:
    • 任何变量都不应该持有一个指向具体类的引用。
    • 任何类都不应该从具体类派生,而是派生一个抽象类。
    • 任何方法都不应该覆盖它的任何基类中已经实现了的方法。

原则四:类应该对扩展开放,对修改关闭

继续刚才的例子,随着汽车公司业务越来越大,为了满足不同客户的不同需求,对于任一品牌的汽车我们将有不同型号的配置。我们的配置包括气囊(Balloon)、天窗(SkyLight)以及自动加热座椅(HeatedSeats)等。每款汽车的价格为汽车自身价值加上配件的价格。设计如下图 7:


图 7. 第一个实现
图 7. 第一个实现 

Oh, My God! 这是什么?类爆炸?!好可怕的一件事。可以想象出来,这样的设计将来的维护成本又多高。假如我想增加新的配件怎么办,假如我想增加新的品牌又怎么办?

其实我们可以用实例变量和继承来重构上面的设计。见下图 8:


图 8. 第二个实现
图 8. 第二个实现 

我们在超类 Car 里面 cost() 方法计算各种配件的价格,然后在子类里面覆盖 cost() 方法,但是会调用超类 cost 方法得到配件价格和,然后再加上子类汽车的基本价格。


清单 7. Car 的另一种实现

				
public abstract class Car
{protected Balloon balloon;protected SkyLight skyLight;protected HeatedSeat heatedSeat;protected int cost(){int res = 0;if(hasBalloon){res += 25000;}if(hasSkyLight){res += 20000;}if(hasHeatedSeat){res +=10000;}}void setBalloon(Balloon balloon);boolean hasBalloon();….….
}public Benz extends Car
{public Benz(Balloon blloon){this.setBalloon(balloon);}public int cost(){int res= 1000000;res += super.cost();}
}

 

怎么样?看起来好像天衣无缝的解决了我们的问题。然而有下面几个问题需要考虑:

  1. 如果配件的价格发生改变怎么办?
  2. 如果出现新的配件怎么办?
  3. 如果某些配件在某种品牌汽车上不能应用怎么办?比如您在玩具车上装 ABS(自动刹车系统)显然是没有意义的。
  4. 如果我想给我的车安装 4 个气囊而不是一个两个,怎么办?

为什么看起来完美的设计,会有这么多解决不了的问题? 因为它违背我们的设计原则:类应该对扩展开放,对修改关闭。我们的目标是允许类容易扩展。在不修改现有代码的基础上,就可以搭配新的行为。这样设计才可以接受新的功能来应对改变的需求。

该原则最典型的应用就是装饰模式。让我们以装饰模式的思想重构我们上面的实现。

  1. 首先用户需要一辆汽车。那我们就构造一辆裸车,并计算价格。
  2. 用户希望是 BMW, 那我们就把它封装为 BMW,并计算价格。
  3. 用户希望带有气囊,那我们就给我们的 BMW 装饰上气囊,并加上气囊的价格 25000。
  4. 用户希望有天窗,那我们就给我们的 BMW 装饰上天窗,并加上气囊的价格 20000。
  5. 用户希望有加热椅,那我们就给我们的 BMW 装饰上加热椅,并加上的价格 10000。
  6. 用户希望带有双重气囊,那我们就给我们的 BMW 再装饰上气囊,并加上气囊的价格 25000。

见图 9:


图 9. 装饰过程
图 9. 装饰过程 

参见我们的实现代码。


清单 8. Car 的另一种实现

				
public abstract class Car 
{ protected abstract int cost(); 
} public class Benz extends Car 
{ public int cost() { return 100000; } } public abstract class CarDecorator extends Car 
{ protected abstract int cost(); 
} public class Balloon extends CarDecorator 
{ public Car car; public Balloon(Car car) { this.car = car; } public int cost() { return car.cost() + 25000; } 
} public class SkyLight extends CarDecorator 
{ public Car car; public SkyLight (Car car) { this.car = car; } public int cost() { return car.cost() + 20000; } 
} public class HeatedSeat extends CarDecorator 
{ public Car car; public HeatedSeat (Car car) { this.car = car; } public int cost() { return car.cost(0 + 10000; } 
}

 

下面看看我们的测试类。


清单 9. Car 装饰者的测试类

				
public class CarWithDecorator {public static void mian(String[] args){Car car = new BMW();car = new Balloon(car);car = new SkyLight(car);car = new HeatedSeat(car);car = new Balloon(car);System.out.println(car.cost());}...}

 

怎么样?回过头,想一想我们前面提出的那四个问题,是用这种设计方式是不是可以很好地解决呢?

总结

  1. 恭喜您,您学会了装饰模式。
  2. 现实世界中,装饰模式,也即我们面向扩展开放,面向修改关闭的应用很多。最常见的就是 Java I/O。见下图 10: 

    图 10. Java I/O
    图 10. Java I/O 

  3. 应用开放封闭原则,有时候会带来小类过多的情况,这是这个原则所带来的潜在问题。所以在实际应用中也要注意设计上的考虑。而不要一味的遵循。

结篇

设计原则不是统一的,不同人对有不同的设计原则有不同的见解,设计原则也不限于上面所陈述的几点。然后设计原则大的方向是统一的,那就是让代码尽可能的应对变化,尽可能的可复用。设计模式不是万能的,没有设计模式也不是不能的。然而在程序设计过程中遵循一些最基本的设计原则则是一个优秀的程序员所必需的,良好的设计原则的应用可以让您设计的程序从容应对可能的改变,可以让您的代码变得优雅而富有艺术性。

 

参考资料

学习

  • 参考 设计模式概述,了解设计模式基本内容。 

  • 查看教程“Java 设计模式 101”,了解设计模式基本词汇以及简单使用设计模式。 

  • 查看教程“Java 设计模式 201:超越四人组”,了解设计模式深层次的应用。 

  • 查看系列文章“从 Java 类库看设计模式”,了解 JDK 在设计模式中的应用。 

  • 技术书店:浏览关于这些和其他技术主题的图书。

  • developerWorks Java 技术专区:数百篇关于 Java 编程各个方面的文章。

讨论

  • 加入 My developerWorks 中文社区。

关于作者

刘旭进,IBM 中国开发中心软件工程师,对开源软件、REST、Web Service、Open Search 有浓厚兴趣和深入研究。目前在 Lotus Connections Fils Team 从事 REST Service 开发相关的工作。

转载于:https://www.cnblogs.com/persist/p/3181240.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/301192.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

为什么中国天才都往美国跑,可美国人的数学那么槽糕

中国天才少年尹希,17岁时收到哈佛大学博士offer,31岁成为哈佛最年轻华人正教授。中国年轻科学家、未来科学大奖数学与计算机奖获得者许晨阳,于2018年加入美国麻省理工,选择去世界顶尖的地方看看。22岁中国“神童”曹原&#xff0c…

如何使用VIM的Help

很多时候在用到vim的命令的时候,都会去网上搜索,殊不知,如果熟练使用VIM的help,可以达到事半功倍的效果。 下面介绍如何使用VIM的help: 1. 在vim的一般模式中输入:help可以进入vim的help界面 这里面注…

可编程智能小车,100种玩法,从3岁玩到15岁,培养孩子“最强大脑”

▲数据汪特别推荐点击上图进入玩酷屋毫无疑问,数学、科学和计算机科学是解决21世纪现代问题的三大支柱。当现在各式各样的兴趣班和教育辅导班快要呈现饱和状态时,一种新兴的教育活动正如火如荼地进行着那就是少儿编程。少儿编程奇迹般的红火,…

php 数组什么情况下是空的?

转载于:https://www.cnblogs.com/persist/p/3183819.html

WPF实现时间轴(仿Gitee)

WPF开发者QQ群: 340500857 | 微信群 -> 进入公众号主页 加入组织“ 前言,接着上一篇圆形菜单。”欢迎转发、分享、点赞、在看,谢谢~。 01—效果预览效果预览(更多效果请下载源码体验):02—代码如下一、…

java如何用键盘输入_java中如何从键盘输入(附代码)

一、java不像C中拥有scanf这样功能强大的函数,大多是通过定义输入输出流对象。常用的类有BufferedReader,Scanner。相关java视频教程推荐:java实例程序:视频教程1、利用 Scanner 实现从键盘读入integer或float 型数据//import jav…

JS partial-application

为什么80%的码农都做不了架构师?>>> /* Title: Partial applicationDescription: the process of fixing a number of arguments to a function, producing another function of smaller arity */var partialAny (function(aps) {// This function wil…

每日一笑 | 我写了一段代码,为什么不能运行呢?

全世界只有3.14 % 的人关注了数据与算法之美(图源网络,侵权删)

使用IQueryable扩展方法实现复杂查询条件

问题在业务开发中&#xff0c;经常要处理比较复杂的查询条件&#xff0c;如下图&#xff1a;如果任一输入有值&#xff0c;则必须作为查询条件之一。示例代码如下&#xff1a;IQueryable<User> query repository.GetAll();if(name!null) {query query.Where(p>p.Name…

阿里日均纳税超1.4亿;AI换脸骗过美侦查;日本民众哄抢令和报纸;辟谣教学楼发现大量金矿;上海拨通首个5G通话;这就是今日大新闻...

今天是4月2日农历二月廿七今天星期二下面是今天的大新闻阿里巴巴日均纳税超1.4亿&#xff08;IT168&#xff09;4月1日&#xff0c;阿里巴巴在“2020财年首日”发布消息称&#xff1a;2018全年&#xff0c;阿里巴巴集团和蚂蚁金服集团总计向国家纳税516亿元&#xff0c;同比增长…

大家好!

在博客园申请帐号已经有好长时间了&#xff0c;可是一直也没有写点什么&#xff0c;可能是太忙了吧&#xff08;其实是懒&#xff09;&#xff01;以后我会多写些文字&#xff0c;毕竟这是一件好事。转载于:https://www.cnblogs.com/hubin/archive/2004/08/16/33928.html

腾讯大湘网某处csrf(city.hn.qq.com)可投诉刷留言

触发点&#xff1a; http://city.hn.qq.com http://city.hn.qq.com/auto/cshop&mbbs&id668 POST /msgboard/message.php HTTP/1.1 Host: c1.city.qq.com Connection: keep-alive Content-Length: 201 Cache-Control: max-age0 Accept: text/html,application/xhtmlxml,…

c#:细说时区、DateTime和DateTimeOffset在国际化中的应用

先说下结论&#xff1a;如果系统不考虑全球化的话&#xff0c;那么我们不用考虑时区的问题&#xff0c;因为我们可以认为中国境内的计算机全部用的是北京时间。1. 时区的来源和划分地球自转一圈是360度&#xff0c;共24小时&#xff0c;所以1小时15度&#xff0c;即&#xff1a…

超赞的“数据与算法之美”资料分享!

相信&#xff0c;一直关注着我们的同学们都知道&#xff0c;小思妹分享了好多好多的资料给大家。为了方便新来的同学自取&#xff0c;小思妹又重新整理了一遍&#xff0c;直接点以下标题即可跳转&#xff01;这是我见过的最全的训练数据集&#xff0c;没有之一&#xff01;送你…

在ASP.NET Core微服务架构下使用数据库切分和扩展, 并用JMeter进行负载测试

原文链接&#xff1a;https://itnext.io/how-to-scale-an-asp-net-core-microservice-and-sharded-database-load-test-with-jmeter-1a8c7292e7e3现在&#xff0c;您将扩展应用程序并运行多个微服务和数据库的容器实例。您将使用Docker Compose和HAProxy负载均衡器&#xff1a;…

每日一笑 | 周杰伦到底什么时候才发新专辑?

全世界只有3.14 % 的人关注了数据与算法之美&#xff08;图源网络&#xff0c;侵权删&#xff09;

GARFIELD@10-18-2004

子非猫转载于:https://www.cnblogs.com/rexhost/archive/2004/10/18/53799.html

【荐】牛逼的WPF动画库:XamlFlair

【荐】牛逼的WPF动画库&#xff1a;XamlFlair原文链接&#xff1a;https://github.com/XamlFlair/XamlFlair翻译&#xff1a;沙漠尽头的狼(本文未全文翻译&#xff0c;建议阅读原文了解更多)XamlFlairXamlFlair库的目标是简化常见动画的实现&#xff0c;并允许开发人员使用几行…

java字符串如何输出_java字符串如何输出

在Java编程中&#xff0c;我们常常用 System.out.println(); 来输出字符串。System.out.println();System是一个类&#xff0c;继承自根类Objectout是类PrintStream类实例化的一个对象&#xff0c;且是System类的静态成员变量println()是类PrintStream的成员方法&#xff0c;被…

简单易懂的自动驾驶科普知识

全世界只有3.14 % 的人关注了数据与算法之美有不少人问我人工智能和自动驾驶的技术问题&#xff0c;我作为一个主业是后端开发的老码农可是回答不了啊&#xff01;今天转载一篇自动驾驶大拿写的文章&#xff0c;学习一下。先来一张各大车企自动驾驶技术的分级图&#xff0c;大致…