前言
关于设计模式,想必大家的第一感觉就是过于高深,有点虚吧。相对来说,我们还是更熟悉ssh或者ssm之类的开发框架,一个工具用久了就会熟能生巧,就像刷漆工,时间长了也知道如何刷的一手漂亮的好墙。我们把springmvc用的很溜,把mybatis用的很溜,但这只是一个合格的程序员应该具备的能力之一。于是我们便成为了代码工人,想站在更高角度去设计,设计模式是必不可少的。
简介
废话那么多,我们直接进入正题呢,到底什么是设计模式呢?我在设计模式之禅上摘抄出这一句话:
设计模式(Designpattern)是一套理论,由软件界的先辈们总结出的一套可以反复使用的经验,它可以提高代码的可重用性,增强系统的可维护性,以及解决一系列的复杂问题。
为什么要学习设计模式
因为要装逼啊…咳咳,大家请忽略前面那句话。相信大家都有阅读spring源码的经历吧,看到类层次设计图,为什么会有这么多的接口,这么多的抽象类,这么多实现,心里有一万只草泥马狂奔而过。spring处处都是设计模式的体现,所以若想攻下spring,设计模式是必学的。其次,作为一个coder,最烦的就是需求变更,我们可以分析现有的需求,预测可能发生的变更,但是我们不能控制需求的变更。既然需求不可变更,那如何拥抱变化呢?设计模式给了我们知道,提出了6大原则,在此基础上提出了23种设计模式。如果你通晓了23种设计模式,那你就可以站在一个更高的层次去赏析程序代码,完成从代码工人到架构师的转变~>~.
设计模式六大原则
单一职责原则(最容易理解,最难实施的一个原则)
描述:应该有且仅有一个原因引起类的变更
相信大家已经很不屑了,切,这么简单的东西还拿出来说。可是道理简单实现难啊。下面我给大家示例一些代码:
public interface IUserInfo {
void setUserName(String name);
void setUserAge(int age);
void walk();
void seeMovie();
void addRole(Role role);
}
相信初级程序员都不会犯这样的错,将用户信息和用户行为放在同一个类里。最佳的做法就是将用户信息和用户行为分开管理,加角色动作也应该放在相应的实现类里。这是我临时想的例子,虽然很简单,但足以表达单一原则的意思。单一职责原则最难划分的就是职责,职责没有量化的标准,职责划分的过于细,类的数量会暴增,系统复杂度会以指数趋势增长,所以建议是接口一定要做到单一职责,累类设计尽量做到单一职责。
里氏替换原则(使用时一定要注意子类的个性化..)
描述:所有引用基类的地方必须能透明地使用其子类的对象。
java与c++不同,java是一门单根继承的语言。使用extend关键字声明继承,若不使用,则代表继承Object。子类继承父类,会拥有父类的方法和属性,很多开源框架的扩展接口都是通过继承父类来完成的。通过这个特性,我们明白只要父类存在的地方,我们都可以替换成子类,并且编译器不会报错。但是java允许子类重写父类方法,这种子类的个性化有时会严重违背里氏替换原则.给大家上个示例:
public class AnyClazz{
//有某一个方法,使用了一个父类类型
public void someoneMethod(Parent parent){
parent.method();
}
}
public class Parent {
public void method(){
System.out.println("parent method");
}
}
public class SubClass extends Parent{
//结果某一个子类重写了父类的方法,说不支持该操作了
public void method() {
throw new Exception();
}
}
public class Test{
public static void main(String[] args) {
AnyClazz anyClazz = new AnyClazz();
anyClazz.someoneMethod(new Parent());
anyClazz.someoneMethod(new SubClass());
}
}
程序运行结果肯定会报错,这就相当于我们埋了一个雷,子类复写父类方法,编译期不报错,运行期就会报错。所以建议在项目中使用此原则时,应尽量避免子类的个性,一旦子类有个性,子类和父类的矛盾将可能直接破坏整个程序。
依赖倒置原则(很常见,现在的企业开发中业务层都有对应接口)
描述:面向接口编程。
在Java中的语言表现就是:模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类发生的。接口和抽象类不依赖于实现类。实现类依赖接口或抽象类。简而言之,就是之前的描述~~。给大家举个例子:
public class Cooker {
public void work(){
System.out.println("我会做饭");
}
}
public class Boss {
public void recruitPerson (Cooker cooker){
cooker.work();
}
}
public class Test {
public static void main(String[] args) {
Boss boss = new Boss();
boss.recruitPerson(new Cooker());
}
}
运行结果:我会做饭
上面代码一个大老板刚开了一个超市,在招厨师。假如我们要是招大堂经理怎么办?创建经理类,在boss中增加招经理的方法同时在test中修改。这样改好吗?仅仅是需求改了一点点,我们就要在所有代码里进行一些改动,这代价也太大了吧,耦合性忒高了~
不要着急,依赖倒置原则可以帮我们解决这个问题,它可以减少类间的耦合性,提高系统的稳定性,降低并行开发引起的风险,提高代码可读性和维护性。修改之后的例子如下:
public interface IWorker {
void work();
}
public class Cooker implements IWorker {
@Override
public void work(){
System.out.println("我会做饭");
}
}
public class Manager implements IWorker {
@Override
public void work() {
System.out.println("糊弄老板,指挥其他人干活");
}
}
public interface IBoss {
void recruitPerson(IWorker iWorker);
}
public class Boss implements IBoss{
@Override
public void recruitPerson(IWorker iWorker) {
iWorker.work();
}
}
public class Test {
public static void main(String[] args) {
Boss boss = new Boss();
IWorker iWorker = new Manager();
boss.recruitPerson(iWorker);
}
}
运行结果:
糊弄老板,指挥其他人干活
咋一看,类变得更多了..使用技术总是要有点成本的吗,但假如以后我们在增加招理货员,招收银员等等改动就小了。其实这个原则的本质就是通过抽象是各个类和模块的实现独立,不互相影响,实现松耦合。
接口隔离原则(六大原则中身材最好的那个小姐姐)
描述:接口尽量细化,同时接口中方法尽量少。
看到上面的描述可能会有点疑惑,这个和第一个的小姐姐(单一职责原则)有点像啊。单一职责衡量的是类的和接口职责要单一,对,是职责!而接口隔离原则就只是要求接口的粒度尽可能的小,最好就是一个接口一个方法(这是完美符合接口隔离原则的),不过这样做的话是明显要被其他小伙伴干死的(2333~~~)。接口粒度太小,接口数就会暴增,开发人员呛死在接口的海洋里,接口粒度太大,灵活性降低,无法提供定制服务。给大家举个例子:
//美食
public interface ICate {
void goodLool();
void niceTaste();
void cleanFood();
}
public class Cate implements ICate{
@Override
public void goodLool() {
System.out.println("色相看起来很好O(∩_∩)O哈哈~");
}
@Override
public void niceTaste() {
System.out.println("吃起来,味绝了(*^▽^*)");
}
@Override
public void cleanFood() {
System.out.println("食材很干净,食用放心");
}
}
public abstract class AbstractPerson {
protected ICate iCate;
public AbstractPerson(ICate iCate) {
this.iCate = iCate;
}
abstract void eat();
}
public class Person extends AbstractPerson {
public Person(ICate iCate) {
super(iCate);
}
@Override
void eat() {
iCate.cleanFood();
iCate.goodLool();
iCate.niceTaste();
}
}
public class Test {
public static void main(String[] args) {
ICate iCate = new Cate();
AbstractPerson abstractPerson = new Person(iCate);
abstractPerson.eat();
}
}
运行结果:
食材很干净,食用放心
色相看起来很好O(∩_∩)O哈哈~
吃起来,味绝了(*^▽^*)
上面的例子我去吃美食,我对美食的标准是看起来,闻起来像,吃起来放心(吃货up!up!)。可是每个人对美食的标准都是一样的,你总不能因为臭豆腐闻起来很臭就不是美食吧。可是现在接口定义了只有三个条件都满足才能叫美食,这个该怎么办?难道要再扩展一个美食接口,只定义味道标准,可是我们的抽象类依赖的是ICate接口,我们的person又怎么只根据味道去分辨是不是美食呢?我们采用接口隔离原则去细化接口,示例如下:
public interface IGoodLookCate {
void goodLook();
}
public interface IGoodTasteCate {
void niceTaste();
void cleanFood();
}
public class Cate implements IGoodLookCate,IGoodTasteCate{
@Override
public void goodLook() {
System.out.println("色相看起来很好O(∩_∩)O哈哈~");
}
@Override
public void niceTaste() {
System.out.println("吃起来,味绝了(*^▽^*)");
}
@Override
public void cleanFood() {
System.out.println("食材很干净,食用放心");
}
}
通过这样的重构后,不仅是要好吃的美食还是要好看的美食,都可以保持接口的稳定。当然你如果要说美食的标准又变了,接口还得改。没错,但是设计是有限度的,不能无限考虑未来,不然会陷入到过度设计的泥潭当中。
迪米特法则(六大原则中最害羞的小姐姐)
描述:一个对象尽量不要知道太多关于其他对象的事
这个原则的核心观念就是类间解耦,低耦合。通俗一点来讲就是1.只和朋友交流2.朋友之间也是有距离的。举个例子说明下吧:
public class Goods {
}
public class Worker {
public void sendGoods(List<Goods> list){
System.out.println("送货喽:"+list);
}
}
public class Boss {
private static final List<Goods> list = new ArrayList<Goods>();
static {
list.add(new Goods());
list.add(new Goods());
list.add(new Goods());
}
public static void main(String[] args) {
Worker worker = new Worker();
worker.sendGoods(list);
}
}
工人送货的场景,老板和工人是直接关系,工人和货物是直接关系,但是代码中老板和货物也搅在了一起,出现了过多依赖,破坏了老板类的健壮性,已经违反了迪米特法则。正确做法应该是将货物的逻辑抽取到工人当中(因为非常简单,就不上代码示例了)。朋友之间也是有距离的应当如何理解呢?假如类A提供了getA(),getB(),getC()三个方法,类B在逻辑内对ABC三个方法进行调用或者根据其返回值决定逻辑,这样也违反了迪米特法则,正确做法应是将对ABC三个方法调用的逻辑抽取封装到A中,同时将ABC三个方法进行私有化。
开闭原则(六大原则中当之无愧的大姐大)
描述:对扩展开放,对修改关闭
开闭原则是六大原则里的精神领袖,也是最虚的原则。前面五个原则就是对开闭原则的具体解释。意思就是现在的系统中,不需要修改原有的代码,只需要扩展一些新的实现就可以搞定。但是没有任何一个系统可以做到,哪怕是spring也不可以,虽说他的扩展性已经强的变态。
以上6个原则是为了之后23种设计模式的描述,基本上都是我自己的理解,或许形容有好有差,不需要在意,各位完全可以有自己的理解。