封装的重要性及其原理

本文通过设计问题引出了封装的重要性,学习了封装的原理,了解了访问器及调制器的使用。使用工厂方法设计模式和代码到接口。通过学习三种不同的实现类,且每个版本都呈现相同的公共接口,是因为封装隐藏了实现的细节。因此,学习封装的重要性及其原理必不可少。
展开查看详情

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

2.A Design Problem Class Date Time as the number of milliseconds since January 1, 1970 at 00:00:00. Convenience member functions Abstract class Calendar For a given a Date object, determine the values of fields year, month, and day. Getter and setter Subclasses Gregorian , Lunar , Mayan , etc. 2 bool after(Date *other) bool before(Date *other) int get( int field) void set( int field, int value)

3.A Design Problem , cont’d Use the factory method design pattern and code to the interface . 3 class CalendarFactory { public: static Calendar *create( int type) { switch (type) { case GREGORIAN : return new GregorianCalendar (); case LUNAR : return new LunarCalendar (); ... } } } Calendar * cal = CalendarFactory ::create(type) ; cal ->set(Calendar::YEAR, 2018); cal ->set(Calendar::MONTH, Calendar::SEPTEMBER); cal ->set(Calendar::DATE, 11);

4.A Design Problem , cont’d Class Day Represent a given day in the Gregorian calendar. Constructor Getters Public date arithmetic member functions 4 Day( int year, int month, int date) int days_from (Day *d) Day * add_days ( int count) int get_year () int get_month () int get_date ()

5.A Design Problem , cont’d Class Day member functions days_from () and add_days () are not trivial ! April, June, September, November have 30 days. February has 28 days, except in leap years when it has 29 days. All other months have 31 days. Leap years are divisible by 4, except that after 1582, years divisible by 100 but not 400 are not leap years. There is no year 0; year 1 is preceded by year -1. In the switchover to the Gregorian calendar, ten days were dropped: October 15, 1582 is preceded by October 4. 5

6.6 Class Day Implementation, Version 1 Private fields Private helper functions Why should the helper functions be private? Don’t clutter the public interface. Don’t trust the user to call them in the right order. Don’t expose a particular implementation. “Once public, always public.” int year int month int date Day * next_day () Day * previous_day () int compare_to (Day *other) static int days_per_month ( int y, int m) static bool is_leap_year ( int y)

7.7 Class Day Implementation, Version 1 , cont’d Private fields Private helper functions Problems? Inefficient public data arithmetic member functions days_from () and add_days () . Helper functions next_day () and previous_day () do all the dirty work. Extraneous Day objects are created and discarded. int year int month int date Day * next_day () Day * previous_day () int compare_to (Day *other) static int days_per_month ( int y, int m) static bool is_leap_year ( int y)

8.8 Class Day Implementation, Version 2 Instead of the private year, month, and date fields, use a Julian day number , which is the number of days from January 1, 4713 BCE. Now the public date arithmetic member functions days_from () and add_days () are trivial. private: int julian Day::Day * add_days ( int n) {     return new Day( julian + n); } int Day:: days_from (Day other) {     return julian - other.julian ; }

9.9 Class Day Implementation, Version 2 , cont’d New private helper functions do all the dirty work of converting back and forth between Julian numbers and [year, month, date]. Problems? A conversion is required for each access of a year, month, or date value. private: static int to_julian ( const int year, const int month, const int date); static void from_julian ( const int j, int ymd []);

10.10 Class Day Implementation, Version 3 Keep the year , month , date , and julian number fields. Do conversions between [year, month, date] and Julian number only when necessary ( lazy conversion ). Convert to Julian number only when doing date arithmetic. Convert to [year, month, date] only if calling a get() method.

11.11 Class Day Implementation, Version 3 , cont’d Add two private boolean fields and two private methods to keep [year, month, date] and Julian number fields synchronized : private: bool ymd_valid ; bool julian_valid ; void ensure_julian (); void ensure_ymd ();

12.12 Importance of Encapsulation Three different implementations of the Day class! Each version presents the same public interface . Encapsulation hides the implementation details.

13.13 Principles A class should expose as few public fields and public functions as possible. All other fields and functions should be hidden from class users by making them private . Supports reliability and flexibility. In most cases, the fields of a class should be made private and users of the class should only use the public getters and setters .

14.14 Accessors and Mutators An object’s field values together constitute the current state of the object. A getter method reads the object state without changing it. A setter method can change the object state. Don’t necessarily provide a setter for every field. For some classes, setters can be dangerous!

15.15 Dangerous Setter Example: The Day Class Recall that the Day class has fields year , month , day . Should there be public setter methods? public: set_year ( int year); set_month ( int month); set_date ( int date);

16.16 Dangerous Setter Example: The Day Class Suppose that Now we want to move the deadline a month: But since there isn’t a February 31, the GregorianCalendar class could set the date instead to March 3. Day *deadline = new Day( 2019 , 1, 31); deadline-> set_month (2); Surprise!

17.17 Dangerous Setter Example: The Day Class Now suppose we just want to move the deadline one day, from January 31 to February 1: The deadline is set instead to March 1. How did that happen? deadline-> set_month (2); deadline-> set_date (1); Surprise! Day *deadline = new Day( 2019 , 1, 31);

18.18 Dangerous Setter Example: The Day Class So should we always set the date first? The result is not April 30! What is it instead? April 2 deadline-> set_date (30); deadline-> set_month (4); Surprise! Day *deadline = new Day(2019, 2, 1);

19.19 No Surprises! Good software design has few, if any, surprises. Surprises can lead to serious programming errors.

20.20 Immutable Classes When you create an instance using a constructor, you also set the object’s initial state . A class is immutable if after you create an instance using a constructor, you cannot change the state . How can you design an immutable class ? Make all the fields private. Provide getters only, no setters.

21.21 Sharing References to Mutable Objects You have to be extra careful if your program passes around references to mutable objects. If a class contains a field of a mutable type, you might inadvertently change the state of an object that you thought was immutable.

22.22 Sharing References to Mutable Objects , cont’d Example: Consider an Employee class that contains the employee’s social security number and birthdate. The social security number and birthdate of an employee should not change. Therefore, you want Employee objects to be immutable.

23.23 class Employee { private: string ssn ; Calendar *birthdate; public: string get_ssn () const { return ssn ; } Calendar * get_birthdate () const { return birthdate; } } Sharing References to Mutable Objects , cont’d

24.24 Dangerous getter! Solution: Method get_birthdate () should return a reference to a clone of the employee birthdate to protect the birthdate object that’s referenced by the employee object. Calendar * bd = employee-> get_birthdate (); bd ->set( Calendar.YEAR , 2000); Sharing References to Mutable Objects , cont’d

25.25 Dangerous constructor! Solution: The constructor should create a clone of the Calendar value that’s passed in and store the clone into the object. Calendar * bd = new GregorianCalendar (1975, 2, 20); Employee *e = new Employee( " 123-45-6789 " , bd ); bd ->set( Calendar.YEAR , 2000); Sharing References to Mutable Objects , cont’d

26.Immutability is a valuable feature. If you can make your class immutable, you should. 26 Sharing References to Mutable Objects , cont’d

27.27 const Fields Another solution: Declare immutable fields of a class to be const . Example: class Employee { private: const string ssn ; const Calendar *birthdate; public: string get_ssn () const { return ssn ; } Calendar * get_birthdate () const { return birthdate; } }

28.28 const Fields , cont’d The value of a const field cannot change after the object has been constructed. Field birthdate cannot be changed to refer to another birthdate object. class Employee { private: const string ssn ; const Calendar *birthdate; public: string get_ssn () const { return ssn ; } Calendar * get_birthdate () const { return birthdate; } }

29.29 const Fields , cont’d Advantage: It’s a compile-time error if you forget to initialize a const field. Disadvantage: You cannot assign the value of a const field to a variable of the same type. class Employee { private: const string ssn ; const Calendar *birthdate; public: string get_ssn () const { return ssn ; } Calendar * get_birthdate () const { return birthdate; } }