C++ api design 품질

  • 600 views
Uploaded on

 

More in: Technology
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
  • '미래 증명적' 이라는 말은 한국말이야 외국말이야?
    Are you sure you want to
    Your message goes here
No Downloads

Views

Total Views
600
On Slideshare
0
From Embeds
0
Number of Embeds
1

Actions

Shares
Downloads
8
Comments
1
Likes
1

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. C++ API 디자인 Ch.2 품질 아꿈사 Cecil
  • 2. 이 장에서는 좋은 API가 갖춰야 할 기본 품질은?
  • 3. 고품질의 API 설계를 가능하게 하는 특정한 품질 속성이 존재하고, 가능하다면 이 기준을 도입해라 ! 설계 품질을 낮추는 많은 요인은 반드시 피해라
  • 4. 고품질 API 설계를 위한 이 책에서 제시하는 기준 • 문제 도메인 • 구체적인 구현 숨기기 • 작게 완성하기 • 쉬운 사용성 • 느슨한 연결 • 안정화와 문서화, 테스트
  • 5. 문제 도메인 모델 API는 문제에 대한 납득할 만한 해결책을 제공해야 한다
  • 6. 훌륭한 추상화 제공 • API는 논리적인 추상화를 제공해야 함 • • 저수준의 코드 구현 보다 추상화된 API SET을 제공 But, 훌륭한 추상화 제공은 쉽지 않은 문제 • 일관성 있고 충분한 논리성을 바탕으로 API를 개발해야 함
  • 7. 핵심 객체 모델링 • API는 문제 도메인의 핵심 객체를 모델링 해야함 • • 객체 모델링의 주요 목표: 주요 객체 식별 및 연결 관계 확인 API의 핵심 모델은 시간이 지나면 변화가 필요함을 고려해야 함 • 너무 앞서서 필요한 것보다 일반적인 객체 생성은 금물
  • 8. 구체적인 구현 숨기기 클라이언트에 영향을 미치지 않고, 내부 로직을 변경할 수 있도록 구체적인 구현을 숨겨야 한다
  • 9. 물리적 은닉: 선언 vs 정의 • 선언: 컴파일러에서 심벌의 타입과 이름을 알려줌 • • 정의: 전체적인 세부 사항을 제공 • • 변수 정의 및 함수의 본문 물리적 은닉 • • ex) external int i, class MyClass … 공개된 인터페이스로부터 분리된 파일에 상세한 내부 로직을 구현 가급적이면 API 헤더는 선언만을 제공 • inline 함수 사용 자제
  • 10. 논리적 은닉: 캡슐화 • API의 구체적인 로직이 외부로 노출되지 않도록 접근 제한자를 사용 • C++ 접근 제한자 • public: 외부에서 접근이 가능 • 구조체의 기본 접근 수준 • protected: 클래스와 파생된 클래스 내부에서만 접근 가능 • private: 클래스 내부에서만 접근 가능 • 클래스의 기본 접근 수준
  • 11. 멤버 변수 감추기 • 멤버 변수의 직접 노출보다 getter/setter 메서드를 제공 • getter/setter의 장점 • • 지연된 평가 • 캐싱 • 추가 연산 • 알림 • 디버깅 • 동기화 • 훌륭한 접근 제어 • • 유효성 체크 바뀌지 않는 관계 유지 클래스의 데이터 멤버는 항상 private로 선언
  • 12. 메서드 구현 숨기기 • public으로 선언할 필요가 없는 메서드를 감추는 것이 중요 • 클래스는 “무엇을 할 것인지를 정의하는 것” • C++의 제약 사항 • 클래스를 구현하기 위해 필요한 모든 멤버를 헤더에 선언 해야함 • 해결방안 • • Pimple 이디엄 사용 or cpp 파일에 정적 함수를 사용 Tip: private 멤버에 대한 비상수 포인터나 참조를 리턴(X:캡슐화 위반)
  • 13. 클래스 구현 숨기기 • 실제 구현 코드를 가능하면 감춰라. #include<vector> ! class Fireworks { public: Fireworks(); ! ! ! }; void void void void SetOrigin(double x, double y); SetColor(float r, float g, float b); SetGravity(float g); SetNumberOfParticles(int num); void Start(); void Stop(); void Next Frame(float dt); private: class FireParticle { public: double mX, mY; double mLifeTime; }; double mOriginX, mOriginY; float mRed, mGreen, mBlue; float mGravity; float mSpeed; bool mIsActive; std::vector<FireParticle *> mParticles; FireParticle을 외부에 구현하기 보다는 내부 클래스로 구현
  • 14. 작게 완성하기 좋은 API라면 최소한의 크기로 완성되어야 한다
  • 15. 지나친 약속은 금지 • API의 모든 public 인터페이스는 약속이다 • 새로운 기능 추가는 쉽지만 기존의 것의 변경이 어렵다 • “더더더” 일반적인 해결책을 찾기 위한 노력을 피해야 하는 이유 • 보다 더한 일반화가 필요한 순간은 오지 않을 수 있다 • 만약 그런날이 온다면, 경험으로 인한 더 좋은 해결책을 내놓을 수 있다 • 추가 기능이 필요하다면, 복잡한 곳보다는 간단한 API에 추가하는 것이 쉽다
  • 16. 가상 함수의 추가는 신중하게 • 상속과 추상화 • • 의도 했던 것 보다 더 많은 기능들을 노출시킬 수 있는 방법 상속의 함정 • “깨지기 쉬운 베이스 클래스 문제”: 베이스 클래스의 변경이 클라이언트에 영향을 줌 • 클라이언트는 API 개발자가 의도치 않았던 방법으로 API를 사용할 수 있다 • 클라이언트는 API를 오류가 많이 발생하도록 확장할 수 있다 (동기화 등) • 클래스 통합을 방해: 기존 함수의 정책에 위반되는 행위를 수행할 경우
  • 17. C++ 가상 함수 사용시 생각할 점 • 가상 함수 호출은 런타임시 vtable을 탐색 • 가상함수를 사용할 수록 객체의 크기도 비례해서 증가(vtable) • 가상 함수는 인라인이 될 수 없다 • 가상 함수를 오버로드 하는 것은 어렵다 • 추가적으로 기억할 것 • 소멸자는 항상 가상 함수로 선언 • 메소드 호출 관계를 문서화 • 생성자나 호출자에서는 절대 가상함수를 호출하지 않음
  • 18. 편리한 API • 기능에 초점을 맞춘 순수 API 제공 vs. 편리한 래퍼 API 제공 • 순수 API 제공 • • 래퍼 API 제공 • • 경량화되고 기능에 집중, 구현 코드의 복잡성을 줄임 클라이언트는 적은 양의 코드를 통해서 기본적인 기능이 동작 2.4 Easy to use 39 최소화시킨 핵심 API를 기반으로 분리된 모듈이나 라이브러리를 통해서 사용하기 편리한 API를 제공 FIGURE 2.4 An example of a core API (OpenGL) separated from convenience APIs layered on top of it (GLU and GLUT). GLUquadric *qobj gluNewQuadric();
  • 19. 쉬운 사용성 잘 설계한 API라면 간단한 작업을 쉽고 명확하게 만들어야 한다
  • 20. 한눈에 들어오는 • 사용자가 API를 어떻게 사용해야 할지 한눈에 이해 • 클래스와 함수 이름을 잘 선택해서 직관적이고 논리적인 객체를 모델 • 자세한 내용은 4장에서
  • 21. names in Chapter 4 when I discuss API design techniques. Avoiding the use of abbreviations can also play a factor in discoverability (Blanchette, 2008) so that users don’t have to remember if your API uses GetCurrentValue(), GetCurrValue(), GetCurValue(), or GetCurVal(). 2.4.2 Difficult to Misuse A good 사용하기에도 use, should also be difficult being easy to 어렵게 Scott 잘못API, inis addition toimportant general interface design guideline to misuse.2004). Meyersofsuggests that this the most (Meyers, Some the most common ways to misuse an API include passing the wrong arguments to a method or passing illegal values to a method. These can happen when you have multiple arguments of the same type and the user forgets the correct order of the arguments or where you use an int to represent a small •range of values instead of a more constrained enum type (Bloch, 2008). For example, consider the following method signature: 좋은 API라면 잘못 사용하기에도 어려워야 함 std::string FindString(const std::string &text, bool search forward, <boolean 사용> bool case sensitive); 2.4 Easy to use 41 ! It would be easy for users to forget whether the first bool argument is the search direction or the caseenum SearchDirection { the flags in the wrong<enum 사용> sensitivity flag. Passing order would result in unexpected behavior and FORWARD, probably!cause the user to waste a few minutes debugging the problem, until they realized that they BACKWARD had }; transposed the bool arguments. However, you could design the method so that the compiler catches this kind of error for them by introducing a new enum type for each option. For example, enum CaseSensitivity { ! CASE SENSITIVE CASE INSENSITIVE }; std::string FindString(const std::string &text, SearchDirection direction, CaseSensitivity case sensitivity); ! • Not only does this mean that users cannot mix up the order of the two flags, because it would generate a compilation error, but also the code they have to write is now more self-descriptive. Compare 코드의 가독성을true, false); 위해서 Boolean보다는 enum을 사용 높이기 result FindString(text, with • Tip: 함수에 같은 타입의 파라미터를 여러개 사용하지 말자 result FindString(text, FORWARD, CASE INSENSITIVE); TIP Prefer enums to booleans to improve code readability.
  • 22. abbreviations in several of its method names, such as prevValue() and previousSibling(). This is another example of why the use of abbreviations should be avoided at all costs. use 2.4 Easy to 43 The use of consistent method signatures is an equally critical design quality. If you have several methods that accept similar argument lists, you should endeavor to keep a consistent number and order for those arguments. To give a counterexample, I refer you to the following functions from 2.4.3 Consistent the standard C library: 일관성 있는 A good API should apply a consistent design approach so that its conventions are easy to remember, and void bcopy(const void *s1, void 2008).size applies to all aspects of API design, such as therefore easy to adopt (Blanchette, *s2, This t n); char *strncpy(char *restrict s1, const char *restrict s2, size t n); • naming conventions, parameter order, the use of standard patterns, memory model semantics, the use Both of these error handling, and so on. of exceptions, functions involve copying n bytes of data from one area of memory to another. In terms of bcopy() these, consistent data from s1 into imply reuse strncpy() copies the However, the the first offunction copies naming conventions s2, whereasof the same words forfrom s2 into same concepts across the API. For example, if you have decided to use the verb pairs Begin and End, s1.•This can give rise to subtle memory errors if a developer were to decide to switch usage between you should not mingle the terms Start and Finish. As another example, the Qt3 API mixes the use of the two functions without a methodreading such as respective man pages. To be sure, there is a clue to close names, of the prevValue() and previousSibling(). This is abbreviations in several of its the conflicting specifications in the function signatures: note the usecosts. const pointer in each case. another example of why the use of abbreviations should be avoided at all of the However, thisconsistent method signatures is won’t be caught by a compiler if the source pointer is not The use of could be missed easily and an equally critical design quality. If you have several declared thatbe const. methods to accept similar argument lists, you should endeavor to keep a consistent number and • order for also the inconsistent useaof the words “copy” and “cpy.” following functions from Note those arguments. To give counterexample, I refer you to the the Let’s take another example from the standard C library. The familiar malloc() function is used to standard C library: API 설계 관점에서의 일관성 명명 규칙, 파라미터의 순서, 표준 패턴의 사용, 의미론적인 메 모리 모델, 예외 및 오류 처리 등. 일관성을 지키지 못한 사례 allocate bcopy(const void *s1, void *s2, and then); void a contiguous block of memory, size t calloc() function performs the same operation with the!char *strncpy(char *restrict s1, const char *restrict zerosize t n); addition that it initializes the reserved memory with s2, bytes. However, despite their similar purpose, they have different function signatures: Both of these functions involve copying n bytes of data from one area of memory to another. However,*calloc(size t count, size t size); s2, whereas strncpy() copies from s2 into void the bcopy() function copies data from s1 into s1.!This can give rise to subtle memory errors if a developer were to decide to switch usage between void *malloc(size t size); the two functions without a close reading of the respective man pages. To be sure, there is a clue to Theconflicting specifications in the a size insignatures:bytes, whereasthe const pointer in eachcount * size) the malloc() function accepts function terms of note the use of calloc() allocates ( case. bytes. In this could be missed inconsistent, this violatesby a compiler if the least surprise.isAs a further However, addition to being easily and won’t be caught the principle of source pointer not •example,tothe const. and write() standard C functions accept a file descriptor as their first paradeclared be read() Note also the the fgets() and fputs() “copy” and “cpy.” meter, whereas inconsistent use of the words functions require the file descriptor to be specified last Let’s take another (Henning, 2009). example from the standard C library. The familiar malloc() function is used to Tip: 함수의 이름, 파라미터의 순서를 일관성 있게 유지하라
  • 23. 44 CHAPTER 2 Qualities 클래스 수준의 일관성 • • • • The STL is a great example of this. The std::vector, std::set, std::map, and classes all offer a size() method to return the number of elements in the cont also all support the use of iterators, once you know how to iterate through a apply the same knowledge to a std::map. This makes it easier to memorize the pr 비슷한 기능을 제공하는 클래스들은 비슷한 인터페이스를 of the API. this kind of consistency for free through polymorphism: by placin 제공 get You tionality into a common base class. However, often it doesn’t make sense for inherit from a common base class, and you shouldn’t introduce a base class pure • ex) STL, std::vector, std::set, std::map의 size 함수 it increases the complexity and class count for your interface. Indeed, it’s n as STL container classes do not inherit from a common base class. Instead, yo design for this by manually identifying the common concepts across your cla 다형성을 적용하면 일관성 얻기가 용이 same conventions to represent these concepts in each class. This is often re polymorphism. You can also make use of C++ templates to help you define and apply this k For example, you could create a template 표현: coordinate class 공통 베이스 클래스를 둘 수 없는 경우에도 각 클래스에 공통된 개념들을 같은 이름으로 for a 2D 정적 다형성and specia floats, and doubles. In this way you are assured that each type of coordinate offe interface. The following code sample offers a simple example of this: C++ 템플릿을 사용한 일관성 유지 • ex) Coord2D<int>, Coord2D<float> .. template <typename T> class Coord2D { public: Coord2D(T x, T y) : mX(x), mY(y) {}; T GetX() const { return mX; } T GetY() const { return mY; } void SetX(T x) { mX void SetY(T y) { mY x; } y; } void Add(T dx, T dy) { mX þ dx; mY þ dy; } void Multiply(T dx, T dy) { mX * dx; mY * dy; } private: T mX; T mY; }; With this template definition, you can create variables of type Coord2D<int>, Coo Coord2D<double> and all of these will have exactly the same interface.
  • 24. 수직적인 • 다른 코드에 영향을 미치지 않는 함수 • • ex) 속성 값을 할당하는 메서드 호출은 그 속성 값만 변화 수직적 API 설계시 고려할 사항 • 중복 제거: 같은 정보가 2가지 이상의 방법으로 반복되지 않게 함 • 독립성 증가: 모든 중첩되는 개념들은 각각의 기반 컨포넌트로 분리 float CheapMotelShower::GetTemperature() const { return mTemperature; } ! float CheapMotelShower::GetPower() const { return mPower; } ! void CheapMotelShower::SetPower(float p) { if (p < 0) p = 0; if (p > 100) p = 100; mPower = p; mTemperature = 42.0f þ sin(p/38.0f) * 45.0f; } float IdealShower::GetTemperature() const { return mTemperature; } ! float IdealShower::GetPower() const { return mPower; } ! void IdealShower::SetTemperature(float t) { if (t < 42) t = 42; if (t > 85) t = 85; mTemperature = t; } void IdealShower::SetPower(float p) { if (p < 0) p = 0; if (p > 100) p = 100; mPower = p; }
  • 25. 견고한 자원 할당 • C++의 메모리 관련 이슈 • • 메모리 이중 해제 • 할당자 혼용 • 잘못된 배열 해제 • • Null 역참조 메모리 누수 관리되는 포인터를 사용 • 공유 포인터: boost::shared_ptr, … • 약한 포인터: boost::weak_ptr, … • 범위 한정 포인터: boost::scoped_ptr, …
  • 26. 플랫폼 독립적 • bool StartCall(const std::string &number); bool EndCall(); #if defined TARGET OS IPHONE bool GetGPSLocation(double &lat, double &lon); #endif }; This poor design creates a different API on different platforms. Doing so your API to introduce the same platform specificity into their own applicatio 2.4 Easy to use 51 the aforementioned case, your clients would have to guard any calls to GetGPSL cisely the same #if conditional statement, otherwise their code may fail to co fined symbol error on other platforms. Furthermore, if in a later version of the API you also add support for anot 2.4.6 Platform Independent Windows Mobile, then you would have to update the #if line in your publ A well-designed C++ API should always avoid platform-specific #if/#ifdef lines in its public. head- your clients would have to find all instances in their co WIN32 WCE Then, ers. If your API presents a high-level and logical model for your problem domain,embedded the TARGET OS IPHONE define and extend it to also include WIN32 as it should, there are very few cases where the API should be different for different platforms. About the only cases you have unwittingly exposed the implementation details of your API. where this may be appropriate are when you are writing an API to interface with a platform-specific Instead, you should hide the fact that the function only works on certain pla resource, such as a routine that draws in a window and requires the appropriate window handle to be method to determine whether the implementation offers the desired capabilitie passed in for the native operating system. Barring these kinds of situations, you shouldFor example, form. never write public header files with platform-specific #ifdef lines. class MobilePhone For example, let’s take the case of an API that encapsulates the functionality offered by a mobile phone. Some mobile phones offer built-in GPS devices that can deliver the geographic location of 2 Qualities 52{ CHAPTER public: the phone, but not all devices offer this capability. However, you should never expose this situation bool StartCall(const std::string &number); directly through your API, such as in the following example: 잘 설계된 API라면 특정 플랫폼에 독립적인 #if/#ifdef 코드를 public 헤더에 사용하지 않아야 함 class MobilePhone { public: bool StartCall(const std::string &number); bool EndCall(); #if defined TARGET OS IPHONE bool GetGPSLocation(double &lat, double &lon); #endif }; bool EndCall(); bool HasGPS() const; Now your API is consistent over all platforms and does not expose the details of wh bool GetGPSLocation(double &lat, double &lon); port GPS coordinates. The client can now write code to check whether the current }; GPS device, by calling HasGPS(), and if so they can call the GetGPSLocation() the actual coordinate. The implementation of the HasGPS() method might look som bool MobilePhone::HasGPS() const { #if defined TARGET OS IPHONE return true; #else This poor design creates a different API on different platforms. Doing so forces the clients of return false; your API to introduce the same platform specificity into their own applications. For example, in #endif the aforementioned case, your clients would have to guard any calls to GetGPSLocation() with pre} cisely the same #if conditional statement, otherwise their code may fail to compile with an unde- This is far superior to the original design because the platform-specific #if statem fined symbol error on other platforms. in the .cpp file instead of being exposed in the header file. Furthermore, if in a later version of the API you also add support for another device class, say Windows Mobile, then you would have to update the #if line in your public header to include TIP WIN32 WCE. Then, your clients would have to find all instances in their code where they have This is platform embedded the TARGET OS IPHONE define and extend it to also include WIN32 WCE.Never put because specific #if or #ifdef statements into your public APIs. It exposes impleme you have unwittingly exposed the implementation details of your API. makes your API appear different on different platforms. Instead, you should hide the fact that the function only works on certain platforms and provide a
  • 27. 느슨한 연결 좋은 API는 느슨한 연결과 높은 결합성을 보인다
  • 28. 이름만을 사용한 연결 • 클래스 전체 선언을 참조할 필요가 없다면 전방 선언을 사용 class MyObject; // only need to know the name of MyObject ! class MyObjectHolder { public: MyObjectHolder(); void SetObject(MyObject *obj); MyObject *GetObject() const; private: MyObject *mObj; };
  • 29. 클래스 연결 줄이기 • 연결 관계를 줄이기 위해 멤버 함수 대신 비멤버, 비프렌드 함수 사용 // myobject.h class MyObject { public: void PrintName() const; std::string GetName() const; ... protected: ... private: std::string mName; ... }; // myobject.h class MyObject { public: std::string GetName() const; ... protected: ... private: std::string mName; ... }; ! void PrintName(const MyObject &obj);
  • 30. 의도적인 중복 • 심각한 연결 관계를 잘라내기 위해 적은 양의 중복코드를 추가하 는 것이 효과적일 경우 #include "ChatUser.h" #include <string> #include <vector> class TextChatLog { public: bool AddMessage(const ChatUser &user, const std::string &msg); int GetCount() const; std::string GetMessage(int index); private: struct ChatEvent { ChatUser mUser; std::string mMessage; size t mTimestamp; }; std::vector<ChatEvent> mChatEvents; }; #include <string> #include <vector> class TextChatLog { public: bool AddMessage(const std::string &user, const std::string &msg); int GetCount() const; std::string GetMessage(int index); private: struct ChatEvent { std::string mUserName; std::string mMessage; size t mTimestamp; }; std::vector<ChatEvent> mChatEvents; }; ChatUser에 대한 연결 관계가 없어짐
  • 31. 58 CHAPTER 2 Qualities 2.5.4 Manager Classes A manager class is one that owns and coordinates several lower-level classes. This can be used to break the dependency of one or more classes upon a collection of low-level classes. For example, consider a structured drawing program that lets you create 2D objects, select objects, and move them around a canvas. The program supports several kinds of input devices to let users select and move objects, such as a mouse, tablet, and joystick. A naive design would require both select and move operations to know about each kind of input device, as shown in the UML diagram (Figure 2.5). Alternatively, you could introduce a manager class to coordinate access to each of the specific input device classes. In this way, the SelectObject and MoveObject classes only need to depend on this single manager class, and then only the manager class needs to depend on the individual input device classes. This may also require creating some form of abstraction for the underlying classes. For example, note that MouseInput, TabletInput, and JoystickInput each have a slightly different interface. Our manager class could therefore put in place a generic input device interface that abstracts away the specifics of a particular device. The improved, more loosely coupled, design is shown in Figure 2.6. Note that this design also scales well too. This is because more input devices can be added to the system without introducing any further dependencies for SelectObject or MoveObject. Also, if you decided to add additional manipulation objects, such as RotateObject and ScaleObject, they only need a single dependency on InputManager instead of each introducing further coupling to the underlying device classes. 매니저 클래스 • 하위 수준에 여러개의 클래스를 포함하면서 중재하는 역할을 수행 • 여러개의 클래스들을 대상으로 하나 혹의 그 이상의 의존성을 줄임 2.5 Loosely coupled MoveObject SelectObject + DoMove() : void + DoSelect() : void MoveObject SelectObject + DoMove() : void + DoSelect() : void InputManager + GetXCoord() : integer + GetYCoord() : integer + IsButton1Down() : boolean + IsButton2Down() : boolean + IsButton3Down() : boolean MouseInput + GetXCoord() : integer + GetYCoord() : integer + IsLMBDown() : boolean + IsMMBDown() : boolean + IsRMBDown() : boolean TabletInput + GetXCoord() : integer + GetYCoord() : integer + IsPenDown() : boolean JoystickInput + GetXCoord() : integer + GetYCoord() : integer + IsTriggerDown() : boolean MouseInput + GetXCoord() : integer + GetYCoord() : integer + IsLMBDown() : boolean + IsMMBDown() : boolean + IsRMBDown() : boolean FIGURE 2.5 TabletInput + GetXCoord() : integer + GetYCoord() : integer + IsPenDown() : boolean Multiple high-level classes each coupled to several low-level classes. FIGURE 2.6 Using a manager class to reduce coupling to lower-level classes. JoystickInput + GetXCoord() : integer + GetYCoord() : integer + IsTriggerDown() : boolean 59
  • 32. 콜백과 옵저버, 알림 • 이벤트가 발생했을때 이를 다른 클래스에 알리는 API • 일반적인 이슈 • 재진입성 • • 수명 관리 • • 알림을 받은 코드가 다시 API를 호출하는 경우를 고려 이벤트에 대한 구독/해지 기능 제공, 또한 중복 이벤트를 받지 않도록 고려 이벤트 순서 • 이벤트의 순서를 명확히 정의 (네이밍 등의 방법으로 명시)
  • 33. 콜백 • 저수준 코드가 고수준의 코드를 사용할때 의존성을 없애는 방법 • 대규모 프로젝트에서 순환 의존 고리를 제거 ! ! ! ! • #include <string> class ModuleB { public: typedef void (*CallbackType)(const std::string &name, void *data); void SetCallback(CallbackType cb, void *data); ... private: CallbackType mCallback; void *mClosure; }; ! if (mCallback) { (*mCallback)("Hello World", mClosure); } 객체지향 프로그램에서 인스턴스 메소드를 콜백으로 사용하는 경우 this 객 체 포인터를 전달해야 함
  • 34. 옵저버 • 콜백은 객체지향에서는 좋은 해결책이 되지 못함 • • this 이슈 좀 더 객체지향적인 해결책 옵저버 • 3장과 4장에서 …
  • 35. 알림 • 콜백과 옵저버는 특정 작업을 위해 생성 • 알림 • 시스템에서 연결되지 않은 부분 사이에서 알림이나 이벤트를 보내는 메커니즘을 중앙화 • 보내는 이와 받는 이 사이의 의존성이 전혀 없음 • ex) signal/slot class MySlot { public: void operator()() const { std::cout << "MySlot called!" << std::endl; } }; // Create an instance of our MySlot class MySlot slot; ! // Create a signal with no arguments and a void return value boost::signal<void ()> signal; ! // Connect our slot to this signal signal.connect(slot); ! // Emit the signal and thereby call all of the slots signal();
  • 36. 안정화와 문서화, 테스트 ! 좋은 API라면 - 안정적이어야 하고, 미래 증명적이어야 함. - 명확히 이해할 수 있게끔 구체적인 문서를 제공해야 함 - 변경이 발생하더라도 기존 기능에 영향이 가지 않도록 자동화된 테스트 프로세스를 갖추어야 함
  • 37. 결론 가능하다면 고품질 API 설계를 가능케하는 기준들을 도입하고, 품질을 낮추는 많은 요인들은 반드시 피해라
  • 38. But, 아무리 잘 정리된 규칙도 모든 상황에 적용 불가 각 프로젝트 특성을 고려한 다음 가장 적합한 해결책을 적용
  • 39. References • Martin Reddy (2014). C++ API 디자인. (천호민, 옮김). 고양시: 지앤선. (원서출판 2013)