本文主要讲述了关于良好设计的要点,没有神奇的公式,每一个好的设计都是在历程之后实现的,这是一个迭代过程。通过讲述迭代的开发和应用开发大图来学习关于良好设计的要点。通过提出一个新的需求,提出解决方案,更好的学习了这项应用。

注脚

展开查看详情

1.CMPE 135: Object-Oriented Analysis and Design August 30 Class Meeting Department of Computer Engineering San Jose State University Fall 2018 Instructor: Ron Mak www.cs.sjsu.edu/~mak 1

2.2 Key Points Regarding Good Design There is no magic formula . Every good design is achieved after a journey. It’s an iterative process . Start with a workable initial design. Each iteration builds upon working code to improve the design.

3.3 Key Points Regarding Good Design , cont’d One good design technique: encapsulation . Encapsulate the code that will vary. Isolate the changes from the rest of the code. Encapsulation supports code reliability and flexibility. The design/code/test iterations are part of the overall “big picture” to develop a software application. No “big bang”!

4.4 Application Development Big Picture

5.5 Iterative Development

6.6 Incremental Development Each iteration adds functionality to code that already works . No Big Bang! Start Goal Head First Object-Oriented Analysis & Design by Brett D. McLaughlin, et al. O’Reilly, 2006.

7.7 Rick’s Guitar Application What made us believe that this is a good design? Head First Object-Oriented Analysis & Design by Brett D. McLaughlin, et al. O’Reilly, 2006. matches( GuitarSpec ): bool

8.8 A New Requirement! Rick changes the name of his shop to “Rick’s Music”. He adds mandolins to the type of instruments he sells. How easily can our software design accommodate this change?

9.9 Possible Mandolin Solutions Add a Mandolin class. Cut and paste code from the existing Guitar class. Replace the Guitar class with a new Instrument class. A type field indicates whether the instrument is a guitar or a mandolin. Make the existing Guitar class and a new Mandolin class subclasses of the Instrument class.

10.10 Possible Mandolin Solutions , cont’d Mandolin class Simple to implement Duplicate code Hard to maintain Instrument class with type field No duplicate code Not an O-O solution Need to check type on objects Solution Advantages Disadvantages Instrument base class O-O solution No type field to check No duplicate code Does Rick have an Instrument in the inventory? Head First Object-Oriented Analysis & Design by Brett D. McLaughlin, et al. O’Reilly, 2006.

11.11 New Instrument Base Class Add a new Instrument base class. Make the existing Guitar class and a new Mandolin class subclasses of the Instrument class. Change the Inventory class to contain a list of Instrument objects instead of only Guitar objects. Each Instrument object can be either a Guitar object or a Mandolin object.

12.12 New Instrument Class , cont’d It doesn’t make sense to create Instrument objects directly. Create only Guitar and Mandolin objects. Therefore, make the Instrument class abstract . You cannot instantiate an abstract class directly. Make the Instrument class the abstract base class of the Guitar and Mandolin subclasses.

13.13 Abstract Classes An abstract class is a placeholder for the actual implementation classes. The abstract class defines the behavior , and the subclasses implement that behavior . The abstract class encapsulates shared behavior and defines the protocol for all its subclasses.

14.Abstract C++ Classes An abstract C++ class contains at least one pure virtual function as a member. A pure virtual function is an abstract function. Example: A subclass of an abstract class defines the pure abstract function. Otherwise, the subclass is also abstract. 14 class AnAbstractClass { public: virtual void f( int ) = 0; // a pure abstract function ... // other functions, not necessarily pure abstract };

15.15 New Instrument Class class Instrument { public:     Instrument(string serial_number , double price, InstrumentSpec *spec)         : serial_number ( serial_number ), price(price), spec(spec)     {}     virtual ~Instrument() = 0;     string get_serial_number () const         { return serial_number ; }     double get_price () const                 { return price; }     void   set_price ( const float new_price ) { price = new_price ; }     InstrumentSpec * get_spec () const         { return spec; } private:     string serial_number ;     double price;     InstrumentSpec *spec; }; inline Instrument::~Instrument() {} Instrument.h We want Instrument to be abstract. None of its other member functions can be abstract, so we make its destructor abstract. We usually define abstract member functions in the subclasses. But even an abstract class must define its own destructor. All instruments have these.

16.16 New InstrumentSpec Class Add a new InstrumentSpec class. Base class for the GuitarSpec and MandolinSpec subclasses. Also make InstrumentSpec abstract, since we only want to create GuitarSpec and MandolinSpec objects. The Instrument class has a one-to-one association with the InstrumentSpec class.

17.17 New InstrumentSpec Class class InstrumentSpec { public:     InstrumentSpec (Builder builder, string model, Type type,                    Wood back_wood , Wood top_wood )         : model(model), builder(builder), type(type),           back_wood ( back_wood ), top_wood ( top_wood )     {}     virtual ~ InstrumentSpec () = 0;     Builder get_builder () const   { return builder; }     string  get_model () const     { return model; }     Type    get_type () const       { return type; }     Wood    get_back_wood () const { return back_wood ; }     Wood    get_top_wood () const   { return top_wood ; }     bool matches( InstrumentSpec * other_spec ); private:     string model;     Builder builder;     Type type;     Wood back_wood , top_wood ;     string to_lower (string str ); }; inline InstrumentSpec ::~ InstrumentSpec () {} Common base class attributes . InstrumentSpec.h

18.18 New InstrumentSpec Class , cont’d bool InstrumentSpec :: matches ( InstrumentSpec * other_spec ) {     if ( builder != other_spec ->builder) return false;     if (   ( model != "")         && ( to_lower (model) != to_lower ( other_spec ->model))) return false;     if ( type != other_spec ->type) return false;     if ( back_wood != other_spec -> back_wood ) return false;     if ( top_wood != other_spec -> top_wood ) return false;     return true; } Match base class attributes only. InstrumentSpec.cpp

19.19 Revised Inventory Class class Inventory { private:     vector<Instrument *> inventory; public:     Inventory() {}     void add_instrument (string serial_number , double price,                         InstrumentSpec *spec);     Instrument *get(string serial_number );     vector<Guitar *>   search( GuitarSpec   * search_spec );     vector<Mandolin *> search( MandolinSpec * search_spec ); }; Inventory.h

20.20 Revised Inventory Class , cont’d void Inventory:: add_instrument (string serial_number , double price,                                InstrumentSpec *spec) {     Instrument *instrument = nullptr ;     if ( typeid (spec) == typeid ( GuitarSpec ))     {         instrument = new Guitar ( serial_number , price,                                 static_cast < GuitarSpec *>(spec));     }     else if ( typeid (spec) == typeid ( MandolinSpec ))     {         instrument = new Mandolin ( serial_number , price,                                 static_cast < MandolinSpec *>(spec));     }     inventory.push_back (instrument); } Inventory.cpp The typeid operator returns a type_info object.

21.21 Revised Inventory Class , cont’d list< Guitar *> Inventory:: search( GuitarSpec * search_spec ) {     list<Guitar *> matching_guitars ;     list<Instrument *>::iterator it;     for (it = inventory.begin (); it != inventory.end (); it++)     {         Guitar *guitar = static_cast <Guitar *>(*it);         GuitarSpec * guitar_spec =                         static_cast < GuitarSpec *>(guitar-> get_spec ());         if ( guitar_spec ->matches( search_spec ) )         {             matching_guitars.push_back (guitar);         }     }     return matching_guitars ; } Search for guitars. Inventory.cpp Overloaded search() functions for guitars and mandolins.

22.22 Revised Inventory Class , cont’d list< Mandolin *> Inventory:: search( MandolinSpec * search_spec ) {     list<Mandolin *> matching_mandolins ;     list<Instrument *>::iterator it;     for (it = inventory.begin (); it != inventory.end (); it++)     {         Mandolin *mandolin = static_cast <Mandolin *>(*it);         MandolinSpec * mandolin_spec =                         static_cast < MandolinSpec *>(mandolin-> get_spec ());         if ( mandolin_spec ->matches( search_spec ) )         {             matching_mandolins.push_back (mandolin);         }     }     return matching_mandolins ; } Overloaded search() functions for guitars and mandolins. Search for mandolins. Inventory.cpp

23.23 Guitar and Mandolin Classes #include <string> #include " Instrument.h " #include " GuitarSpec.h " using namespace std ; class Guitar : public Instrument { public:     Guitar(string serial_number , double price, GuitarSpec *spec); }; #include <string> #include " Instrument.h " #include " MandolinSpec.h " using namespace std ; class Mandolin : public Instrument { public:     Mandolin(string serial_number , double price, MandolinSpec *spec); }; What’s the difference between these two classes? Guitar.h Mandolin.h

24.class GuitarSpec : public InstrumentSpec { public:     GuitarSpec (Builder builder, string model, Type type,                int string_count , Wood back_wood , Wood top_wood )         : InstrumentSpec (builder, model, type, back_wood , top_wood ),           string_count ( string_count )     {}     int get_string_count () const { return string_count ; }     bool matches( InstrumentSpec * other_spec ); private:     int string_count ; }; class MandolinSpec : public InstrumentSpec { public:     MandolinSpec (Builder builder, string model, Type type,                  Style style, Wood back_wood , Wood top_wood )         : InstrumentSpec (builder, model, type, back_wood , top_wood ),           style(style)     {}     Style get_style () const { return style; }     bool matches( InstrumentSpec * other_spec ); private:     Style style; }; GuitarSpec and MandolinSpec Classes 24 enum class Style {     A, F , }; GuitarSpec.h MandolinSpec.h Style.h

25.25 GuitarSpec and MandolinSpec Classes , cont’d bool GuitarSpec ::matches ( InstrumentSpec * other_spec ) {     if ( typeid ( other_spec ) != typeid ( GuitarSpec )) return false;     if (! InstrumentSpec ::matches( other_spec )) return false;     GuitarSpec * guitar_spec = static_cast < GuitarSpec *> ( other_spec );     if ( string_count != guitar_spec -> string_count ) return false;     return true; } Match base class attributes. bool MandolinSpec ::matches ( InstrumentSpec * other_spec ) {     if ( typeid ( other_spec ) != typeid ( MandolinSpec )) return false;     if (! InstrumentSpec ::matches( other_spec )) return false;     MandolinSpec * mandolin_spec = static_cast < MandolinSpec *> ( other_spec );     if (style != mandolin_spec ->style) return false;     return true; } Match base class attributes. Type-specific match. Type-specific match. GuitarSpec.cpp MandolinSpec.cpp Type cast Type cast

26.26 Design for Rick’s Music Head First Object-Oriented Analysis & Design by Brett D. McLaughlin, et al. O’Reilly, 2006.

27.27 Any Red Flags? Duplicated code Guitar and Mandolin classes GuitarSpec and MandolinSpec classes Overloaded Inventory::search() functions list< Guitar *> Inventory::search( GuitarSpec * search_spec ) {     ... } list< Mandolin *> Inventory::search( MandolinSpec * search_spec ) {     ... } Inventory.cpp

28.28 More Red Flags? Serial type tests, such as in Inventory:: add_instrument () : void Inventory:: add_instrument (string serial_number , double price,                                InstrumentSpec *spec) {     ...     if ( typeid (spec) == typeid ( GuitarSpec ) )     ...     else if ( typeid (spec) == typeid ( MandolinSpec ) )     ... } Inventory.cpp

29.29 Rick Wants More Instruments! YIKES! Our design doesn’t scale very well ... Head First Object-Oriented Analysis & Design by Brett D. McLaughlin, et al. O’Reilly, 2006.