Effective C++中文版第三版 高清完整版PDF

有人说C++程序员可以分成两类,读过Effective C++的和没读过的。世界顶级C++大师Scott Meyers成名之作的第三版的确当得起这样的评价。当您读过这本书之后,就获得了迅速提升自己C++功力的一个契机。
在国际上﹐本书所引起的反响﹐波及整个计算技术出版领域﹐余音至今未绝。几乎在所有C+

文件:n459.com/file/25127180-477296466

以下内容无关:

-------------------------------------------分割线---------------------------------------------

工厂模式(Factory Design Pattern)可细分为三种,分别是简单工厂,工厂方法和抽象工厂,它们都是为了更好的创建对象。

所谓的“工厂”,就是用来将创建对象的代码封装起来,因为这部分代码将来变动的几率很大,所以这里的“工厂”的实质作用就是“封装变化”,以便于维护。

其中用到了“针对接口编程,而非针对实现编程”的设计原则。

下面通过一个销售饮料的例子,来对这三种工厂模式进行介绍。

1,简单工厂模式
假如现在有一位老板,想开一家饮料店,该店销售各种口味的饮料,比如苹果味,香蕉味,橙子味等。

将这些饮料用代码来表示,如下:

class Drink {
public void packing() {
//
}
}

class DrinkApple extends Drink {
}

class DrinkBanana extends Drink {
}

class DrinkOrange extends Drink {
}
Drink 类为所有其它味道的饮料的父类。

当有顾客来购买饮料的时候,顾客需要说明想买哪种口味的饮料,然后服务员就去将该口味的饮料取过来,包装好,然后交给顾客。

下面我们用最简单直接的代码,来模拟饮料店和卖饮料的过程,如下:

class DrinkStore {

public Drink sellDrink(String flavor) {
    Drink drink;

    if (flavor.equals("apple")) {
        drink = new DrinkApple();
    } else if (flavor.equals("banana")) {
        drink = new DrinkBanana();
    } else if (flavor.equals("orange")) {
        drink = new DrinkOrange();
    } else {
        drink = new Drink();
    }

    drink.packing();

    return drink;
}

}
但是这种实现方式有个问题,就是当需要下架旧饮料或上架新饮料的时候,会导致下面这部分代码被频繁的修改:

if (flavor.equals(“apple”)) {
drink = new DrinkApple();
} else if (flavor.equals(“banana”)) {
drink = new DrinkBanana();
} else if (flavor.equals(“orange”)) {
drink = new DrinkOrange();
} else {
drink = new Drink();
}
那这就违背了设计原则中的开闭原则:代码应该对扩展开发,对修改关闭。所以我们需要对该代码进行改进,那如何修改呢?

简单工厂模式告诉我们要将类似这样的代码封装到一个类里边,这个类就叫做简单工厂类,该类中提供一个方法,它可以生产我们所需要的各种对象。

下面用代码来模拟这个简单工厂类,如下:

class SimpleDrinkFactory {
public Drink createDrink(String flavor) {
Drink drink;

    // 这段容易被频繁修改的代码,被封装到了工厂类中
    if (flavor.equals("apple")) {
        drink = new DrinkApple();
    } else if (flavor.equals("banana")) {
        drink = new DrinkBanana();
    } else if (flavor.equals("orange")) {
        drink = new DrinkOrange();
    } else {
        drink = new Drink();
    }

    return drink;
}

}
可以看到,createDrink 方法完成了创建 Drink 的任务。

createDrink 方法也可以定义成静态方法,优点是在使用 createDrink 方法时不需要再创建对象,缺点是不能再通过继承的方式来改变 createDrink 方法的行为。

下面来看如何使用 SimpleDrinkFactory 类:

class DrinkStore {
private SimpleDrinkFactory factory;

public DrinkStore(SimpleDrinkFactory factory) {
    this.factory = factory;
}

public Drink sellDrink(String flavor) {
    Drink drink = factory.createDrink(flavor);

    drink.packing();

    return drink;
}

}
可以看到,我们将 SimpleDrinkFactory 类的对象作为 DrinkStore 类的一个属性,经过改进后的 sellDrink 方法就不需要再被频繁修改了。如果再需要上架下架饮料,则去修改简单工厂类 SimpleDrinkFactory 即可。

我将完整的简单工厂代码放在了这里,供大家参考,类图如下:

在这里插入图片描述

2,工厂方法模式
简单工厂模式从严格意义来说并不是一个设计模式,而更像一种编程习惯。

工厂方法模式定义了一个创建对象的接口(该接口是一个抽象方法,也叫做“工厂方法”),但由子类(实现抽象方法)决定要实例化的类是哪个。

在工厂方法模式中,父类并不关心子类的具体实现,但是父类给了子类一个“规范”,让子类必须“生成”父类想要的东西。

工厂方法将类的实例化推迟到了子类中,让子类来控制实例化的细节,也就是将创建对象的过程封装了起来。而真正使用实例的是父类,这样就将实例的“实现”从“使用”中解耦出来。因此,工厂方法模式比简单工厂模式更加有“弹性”。

下面我们来看下如何用工厂方法模式来改进上面的代码。

首先,定义 DrinkStoreAbstract,它是一个抽象父类:

abstract class DrinkStoreAbstract {

// final 防止子类覆盖
public final Drink sellDrink(String flavor) {

    Drink drink = factoryMethod(flavor);    // 使用实例

    drink.packing();

    return drink;
}

// 子类必须实现
protected abstract Drink factoryMethod(String flavor);

}
上面代码中 factoryMethod 方法就是所谓的工厂方法,它是一个抽象方法,子类必须实现该方法。

factoryMethod 方法负责生成对象,使用对象的是父类,而实际生成对象的则是子类。

接下来定义一个具体的 DrinkStore 类,它是 DrinkStoreAbstract 的子类:

class DrinkStore extends DrinkStoreAbstract {

@Override
public Drink factoryMethod(String flavor) {
    Drink drink;

    if (flavor.equals("apple")) {
        drink = new DrinkApple();
    } else if (flavor.equals("banana")) {
        drink = new DrinkBanana();
    } else if (flavor.equals("orange")) {
        drink = new DrinkOrange();
    } else {
        drink = new Drink();
    }

    return drink;
}

}
可以看到,子类中的 factoryMethod 方法有了具体的实现。

如果需要上架下架饮料,则去修改子类中的工厂方法 factoryMethod 即可。而 DrinkStoreAbstract 作为一个“框架”,无须改动。

完整的工厂方法代码放在了这里,供大家参考,类图如下:

在这里插入图片描述

图中的粉色区域是工厂方法模式的重点关注区。

3,依赖倒置原则
在介绍抽象工厂模式之前,我们先来看看什么是依赖倒置原则。

依赖倒置包含了依赖和倒置两个词。

我们先来看看“依赖”,“依赖”是指类与类之间的依赖关系。

在本文刚开始时的 sellDrink 方法是这么写的:

public Drink sellDrink(String flavor) {
Drink drink;

  if (flavor.equals("apple")) {
      drink = new DrinkApple();
  } else if (flavor.equals("banana")) {
      drink = new DrinkBanana();
  } else if (flavor.equals("orange")) {
      drink = new DrinkOrange();
  } else {
      drink = new Drink();
  }

  drink.packing();

  return drink;

}
sellDrink 方法依赖了三个具体类,如果饮料的味道继续增加的话,那么 sellDrink 方法将依赖更多的具体类。

这会导致只要任意一个具体类发生改变,sellDrink 方法就不得不去改变,也就是类 A 需要改变(A 也是一个具体类)。

在这里插入图片描述

在上面这个关系图中,A 称为高层组件,各个具体类称为低层组件,所以在这幅图中,高层组件依赖了低层组件。

依赖倒置原则是指“要依赖抽象,而不依赖具体类”。更具体来说就是,高层组件不应该依赖低层组件,高层组件和低层组件都应该依赖抽象类。

那么怎样才能达到依赖倒置原则呢?工厂方法就可以!

在经过工厂方法模式的改造之后,最终的 DrinkStoreAbstract 类中的 sellDrink 方法变成了下面这样:

public final Drink sellDrink(String flavor) {

  Drink drink = factoryMethod(flavor);    // 使用实例

  drink.packing();

  return drink;

}
这使得 sellDrink 的所在类不再依赖于具体类,而依赖于一个抽象方法 factoryMethod,而 factoryMethod 方法依赖于 Drink,然后,各个具体类也依赖于 Drink,Drink 是一个广义上的“抽象接口”。

这样,高层组件和低层组件都依赖于 Drink 抽象,关系图变成了下面这样:

在这里插入图片描述

之前各个具体类的箭头是向下指的,而现在各个具体类的箭头是向上指的,箭头的方向倒了过来,这就是所谓的依赖倒置。

依赖倒置原则使得高层组件和低层组件都依赖于同一个抽象。

那怎样才能避免违反依赖倒置原则呢?有下面三个指导方针:

变量不要持有具体类的引用。
需要 new 对象的地方,要改为工厂方法。
类不要派生自具体类,而要从抽象类或接口派生。
派生自具体类,就会依赖具体类。
子类不要覆盖父类中已实现的方法。
父类中已实现的方法应该被所有子类共享,而不是覆盖。
当然事情没有绝对的,上面三个指导方针,是应该尽量做到,而不是必须做到。

4,抽象工厂模式
下面来介绍抽象工厂模式。

假如临近春节,饮料店老板为了更好的销售饮料,准备购买一批礼盒,来包装饮料。为了保证礼盒的质量,规定礼盒只能从特定的地方批发,比如北京,上海等。

那我们就定义一个接口,让所有的礼盒都派生自这个接口,如下:

interface DrinkBoxFactory {
String createBox();
}

class BeiJingBoxFactory implements DrinkBoxFactory {

@Override
public String createBox() {
    return "BeijingBox";
}

}

class ShangHaiBoxFactory implements DrinkBoxFactory {

@Override
public String createBox() {
    return "ShangHaiBox";
}

}
下面需要编写 Drink 类,我们让 Drink 类是一个抽象类,如下:

abstract class Drink {
String flavor;
protected abstract void packing();
}
需要注意的是,在抽象类 Drink 中有一个抽象方法 packing,Drink 将 packing 的具体实现交给每个派生类。Drink 类只规定派生类中需要实现一个 packing,但并不关心它的具体实现。

下面编写每种口味的饮料,如下:

class DrinkApple extends Drink {
DrinkBoxFactory boxFactory;

public DrinkApple(DrinkBoxFactory boxFactory) {
    this.boxFactory = boxFactory;
    this.flavor = "DrinkApple";
}

@Override
public void packing() {
    System.out.println(flavor + boxFactory.createBox());
}

}

class DrinkBanana extends Drink {
DrinkBoxFactory boxFactory;

public DrinkBanana(DrinkBoxFactory boxFactory) {
    this.boxFactory = boxFactory;
    this.flavor = "DrinkBanana";
}

@Override
public void packing() {
    System.out.println(flavor + boxFactory.createBox());
}

}

class DrinkOrange extends Drink {
DrinkBoxFactory boxFactory;

public DrinkOrange(DrinkBoxFactory boxFactory) {
    this.boxFactory = boxFactory;
    this.flavor = "DrinkOrange";
}

@Override
public void packing() {
    System.out.println(flavor + boxFactory.createBox());
}

}
可以看到每种口味的饮料中都有一个 boxFactory 对象,在 packing 时,从 boxFactory 中获取礼盒。

注意 boxFactory 是一个接口类对象,而不是一个具体类对象,因此,boxFactory 的具体对象是由客户决定的。

然后,DrinkStoreAbstract 还是沿用工厂方法模式中的定义,如下:

abstract class DrinkStoreAbstract {

// final 防止子类覆盖
public final Drink sellDrink(String flavor) {

    Drink drink = factoryMethod(flavor);    // 使用实例

    drink.packing();

    return drink;
}

// 子类必须实现
protected abstract Drink factoryMethod(String flavor);

}
然后我们实现使用北京礼盒包装的Store 和使用上海礼盒包装的Store,如下:

class BeijingDrinkStore extends DrinkStoreAbstract {

@Override
protected Drink factoryMethod(String flavor) {
    Drink drink = null;
    DrinkBoxFactory factory = new BeiJingBoxFactory();

    if (flavor.equals("apple")) {
        drink = new DrinkApple(factory);
    } else if (flavor.equals("banana")) {
        drink = new DrinkBanana(factory);
    } else if (flavor.equals("orange")) {
        drink = new DrinkOrange(factory);
    } 
            
    return drink;
}

}

class ShangHaiDrinkStore extends DrinkStoreAbstract {

@Override
protected Drink factoryMethod(String flavor) {
    Drink drink = null;
    DrinkBoxFactory factory = new ShangHaiBoxFactory();

    if (flavor.equals("apple")) {
        drink = new DrinkApple(factory);
    } else if (flavor.equals("banana")) {
        drink = new DrinkBanana(factory);
    } else if (flavor.equals("orange")) {
        drink = new DrinkOrange(factory);
    }

    return drink;
}

}
经过这么一些列的改动,我们到底做了些什么呢?事情的起因是老板需要一些礼盒来包装饮料,这就是需求增加了。

因此我们引入了一个抽象工厂,即 DrinkBoxFactory,这个接口是所有礼盒工厂的父类,它给了所有子类一个“规范”。

抽象工厂模式提供了一个接口,用于创建相关对象的家族。该模式旨在为客户提供一个抽象接口(本例中就是 DrinkBoxFactory 接口),从而去创建一些列相关的对象,而不需关心实际生产的具体产品是什么,这样做的好处是让客户从具体的产品中解耦。

最终,客户是这样使用 DrinkStoreAbstract 的,如下:

public class AbstractMethod {
public static void main(String[] args) {
DrinkStoreAbstract beiStore = new BeijingDrinkStore();
beiStore.sellDrink(“apple”);
beiStore.sellDrink(“banana”);
beiStore.sellDrink(“orange”);

    DrinkStoreAbstract shangStore = new ShangHaiDrinkStore();
    shangStore.sellDrink("apple");
    shangStore.sellDrink("banana");
    shangStore.sellDrink("orange");
}

}
完整的抽象工厂模式代码放在了这里,供大家参考,类图如下:

在这里插入图片描述

从该图可以看出,抽象工厂模式比工厂方法模式更复杂了一些,另外仔细观察它们两个的类图,各自所关注的地方(粉红色区域)也是不一样的。

工厂方法与抽象工厂的相同点都是将对象的创建封装起来。不同点是:

工厂方法主要关注将类的实例化推迟到子类中。
抽象工厂主要关注创建一系列相关的产品家族。
5,总结
工厂模式使用到的设计原则有:

针对接口编程,而非针对实现编程。
开闭原则。
依赖倒置原则。
封装变化。

作者 : Scott Meyers 译序、导读 : 侯捷 译序(侯捷) C++ 是一个难学易用的语言! C++ 的难学,不仅在其广博的语法,以及语法背後的语意,以及语意背後的深层思维,以及深层思维背後的物件模型;C++ 的难学,还在於它提供了四种不同(但相辅相成)的程式设计思维模式:procedural-based,object-based,object-oriented,generic paradigm。 世上没有白吃的午餐。又要有效率,又要有弹性,又要前瞻望远,又要回溯相容,又要能治大国,又要能烹小鲜,学习起来当然就不可能太简单。 在如此庞大复杂的机制下,万千使用者前仆後续的动力是:一旦学成,妙用无穷。C++ 相关书籍之多,车载斗量;如天上繁星,如过江之鲫。广博如四库全书者有之(The C++ Programming Language、C++ Primer),深奥如重山复水者有之(The Annotated C++ Reference Manual, Inside the C++ Object Model),细说历史者有之(The Design and Evolution of C++, Ruminations on C++),独沽一味者有之(Polymorphism in C++, Genericity in C++),独树一帜者有之(Design Patterns,Large Scale C++ Software Design, C++ FAQs),程式库大全有之(The C++ Standard Library),另辟蹊径者有之(Generic Programming and the STL),工程经验之累积亦有之(Effective C++, More Effective C++, Exceptional C++)。 这其中,「工程经验之累积」对已具C++ 相当基础的程式员而言,有著致命的吸引力与立竿见影的帮助。Scott Meyers 的Effective C++ 和More Effective C++ 是此类佼佼,Herb Sutter 的Exceptional C++ 则是後起之秀。 这类书籍的一个共通特色是轻薄短小,并且高密度地纳入作者浸淫於C++/OOP 领域多年而广泛的经验。它们不但开展读者的视野,也为读者提供各种C++/OOP 常见问题或易犯错误的解决模型。某些小范围主题诸如「在base classes 中使用virtual destructor」、「令operator= 传回*this 的reference」,可能在百科型C++ 语言书籍中亦曾概略提过,但此类书籍以深度探索的方式,让我们了解问题背後的成因、最佳的解法、以及其他可能的牵扯。至於大范围主题,例如smart pointers, reference counting, proxy classes,double dispatching, 基本上已属design patterns 的层级! 这些都是经验的累积和心血的结晶。
### 回答1: 《Effective C++ 中文第三版》是针对C++程序设计语言的一本重要的专业书籍,它将C++语言的各种语法特性、设计模式及编程技巧进行了深度分析和系统总结。 本书分为55条编程指南,从C++语言的核心概念(如RAII、异常安全等)到编程技巧的细节(如拷贝控制、继承、模板等)都进行了详细介绍。每个指南都包含了对应的问题、建议和说明,通过实际例子和对比分析,让读者能够更好地理解和掌握相关知识。 相比其他的C++规范书籍,《Effective C++ 中文第三版》更加实用和直观,它的重点在于介绍如何写出正确、高效、健壮的C++代码。同时,书中还对C++11和C++14的新特性进行了简单介绍,为读者扩展了视野,帮助读者更好地应对日益复杂的编程需求。 总的来说,《Effective C++ 中文第三版》是一本适合C++程序员的入门和进阶教材,通过系统性的介绍和实例讲解,能够帮助读者逐步掌握C++语言的精髓和技巧,写出更加高效、健壮和易维护的程序。 ### 回答2: 《Effective C++ 中文第三版》是一本介绍C++编程技巧的经典书籍。该书作者Scott Meyers是一位C++专家,他精心编写了该书的内容,用通俗易懂的语言阐述了C++编程的许多细节问题。通过学习这本书,读者可以更好地理解C++的语言特性,掌握C++编程的技巧和方法,以提高程序的质量和效率。 该书涵盖了37个条款,主要分为四个部分。第一部分介绍了C++语言的基础知识,包括构造函数和析构函数、赋值操作、拷贝构造函数等;第二部分介绍了C++的设计和实现,包括类设计、模板使用和异常处理等;第三部分介绍了C++的继承和多态,包括虚函数、抽象类、多重继承、虚继承等;第四部分介绍了C++的高级语言特性,包括模板元编程、异常安全、性能优化和智能指针等。 通过学习这本书,读者可以获得以下几个方面的收获。首先,掌握C++编程的基本技能和知识,能够写出高质量的、健壮的C++程序;其次,了解C++语言的设计和实现原理,能够更好地理解C++程序的内部机制;最后,学会了高效的C++编程技巧和方法,可以提高程序的性能和效率,避免常见的、容易犯的C++编程错误。 总之,《Effective C++ 中文第三版》是一本非常优秀的C++编程书籍,对于想要成为一名优秀的C++程序员的读者来说,是一本不可多得的好书。 ### 回答3: Effective C++是一本非常经典的C++编程技巧指南,被誉为C++编程者必读的参考书之一。作者Scott Meyers深入浅出的将自己多年的实际经验和对C++各个方面的深入理解融合到了书中,为读者提供了各种实用技巧和解决方案。本书被分成了50个小节,每个小节都介绍一个C++编程中的技巧,如何避免陷阱以及如何让代码更加清晰可读。 Effective C++中文第三版在前两版的基础上做了一些更新和补充,和当前主流的C++版本兼容,增加了对多线程编程方面的内容和对垃圾回收的讲解等等。此外,本书还提供了大量的实际例子和细节解释,让读者能够更好地理解和运用这些技巧。不仅适合初学者,对于已经上手C++编程的程序员也是一本非常有价值的参考书,可以帮助他们更好的掌握C++语言,并写出高效、可维护的代码。 总的来说,Effective C++C++编程界的经典书籍之一,具有极高的实用价值和指导意义。不同阶段的程序员都可以从中获益,提高自己的编程能力。因此,对于想要成为一名优秀的C++程序员的人来说,这本书是绝不能缺少的。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值