Try   HackMD

實習課第十二次作業

tags: NTOU CSE C++ Programming

教學文件和作業說明文件: https://hackmd.io/@kogiokka/ntou-cse-cpp-nav

本次實習主題是程式設計中的配接器模式(Adapter Pattern)。還有練習 C++ 的繼承(inheritance)、虛擬函式(virtual functions)和抽象類別(abstract class),以及 std::map 的用法。

Adapter Pattern

配接器模式是一種設計模式(Design Pattern),讓介面不相容的物件能夠溝通。

如下圖,客戶(client)有一個固定存取服務(service)的方式(interface)。如果今天客戶想新增一個服務,但這個服務沒有提供客戶要求的方法,配接器(adapter)就可以為客戶提供跟服務溝通的管道。

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

舉一個簡單的例子,一開始程式中有兩個類別 RoundHoleRoundPeg,分別代表圓孔和圓木樁。RoundHole 類別的物件有一個函式 fit,接受 RoundPeg物件並透過getRadius函式得知圓木樁的半徑,藉此來知道木樁是不是能放進圓孔中。

現在多了一個類別 SquarePeg 代表方木樁。如果我們想知道一個方木樁是不是也能放進圓孔,就必須要讓圓孔也知道它的「半徑」。然而,放一個 getRadius 函式在 SquarePeg 並不怎麼合理,讓圓孔為每一種型狀的木樁定義一個 fit 函式也不理想(必須把轉換的程式碼放在裡面)。這時 adapter 就派上用場了,將轉接的部分與主要的程式邏輯(fit函式)切開來。多寫一個 Adapter 類別的好處是不必為每次新增一種形狀木樁,就去動圓孔甚至是木樁的程式碼。

上述三個類別的關係如下圖,SquarePegAdapter 就是配接器:

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

在這個例子中,圓孔是 Client;木樁是 Service,要負責提供圓孔「告知半徑資訊」的服務。Client 存取 Service 的方式是客戶端介面,Client Interface,就是我們的getRadius函式。所有對客戶提供服務的類別都要符合 Client Interface 的規範。方木樁沒有 getRadius 函式,客戶必須經由 Adapter 存取它的服務(半徑資訊),所以方木樁是我們的 Adaptee,被配接的人。

參考資料

需求

  • 依照配接器模式(Adapter Pattern)實作範例程式所提供的三個類別:AdapteeAdapterClassAdapterObject
  • 通過範例程式提供的測試案例。

範例程式

AdapterPattern

編譯和執行

不是使用 Visual Studio 嗎?請見 CMake專案建置

在 Visual Studio 選擇開啟本機資料夾檔案 → 開啟 → 資料夾,並選擇AdapterPattern/資料夾。Visual Studio 會自動開始設定 CMake 專案。等待輸出欄位的訊息跑完之後,在上方工具列的選取啟動項目adapter.exe 就可以自動編譯執行了。

專案目錄結構

AdapterPattern
├── CMakeLists.txt
└── Source
    ├── Adapter
    │   ├── AdapterClass.cpp
    │   ├── AdapterClass.hpp
    │   ├── AdapterObject.cpp
    │   └── AdapterObject.hpp
    ├── Book.cpp
    ├── Book.hpp
    ├── ClientInterface
    │   └── LookUpTable.hpp
    ├── main.cpp
    └── Service
        ├── Adaptee.cpp
        └── Adaptee.hpp

原始碼

CMakeLists.txt

cmake_minimum_required(VERSION 3.12) project("Adapter Pattern") # 執行 "Source/" 資料夾中的 CMakeLists.txt add_subdirectory("Source/")

Source/Book.cpp

#include "Book.hpp" using std::ostream; using std::string; using std::vector; Book::Book() : m_name("") , m_authors({}) , m_price(0) { } Book::Book(const string& name, const vector<string>& authors, float price) : m_name(name) , m_authors(authors) , m_price(price) { } void Book::assign(const string& name, const vector<string>& authors, float price) { m_name = name; m_authors = authors; m_price = price; } std::string Book::name() const { return m_name; } std::vector<std::string> Book::authors() const { return m_authors; } float Book::price() const { return m_price; }

Source/Book.hpp

#ifndef BOOK_H_ #define BOOK_H_ #include <iostream> #include <string> #include <vector> class Book { friend std::ostream& operator<<(std::ostream&, const Book&); private: std::string m_name; std::vector<std::string> m_authors; float m_price; public: Book(); Book(const std::string& name, const std::vector<std::string>& authors, float price); void assign(const std::string& name, const std::vector<std::string>& authors, float price); std::string name() const; std::vector<std::string> authors() const; float price() const; }; #endif // BOOK_H_

Source/main.cpp

#include <iomanip> // std::quoted #include <iostream> // std::cout, std::endl #include <map> // std::map #include <sstream> // std::osringstream #include <string> // std::string #include <tuple> // std::tie, std::ignore #include <utility> // std::pair #include <vector> // std::vector #include "Adapter/AdapterClass.hpp" #include "Adapter/AdapterObject.hpp" #include "Book.hpp" #include "ClientInterface/LookUpTable.hpp" using std::cout; using std::endl; using std::map; using std::ostream; // Output stream using std::ostringstream; // Output string stream using std::pair; using std::quoted; using std::string; using std::vector; static inline void BANNER(); static inline void HEAD1(const string& text); static inline void HEAD2(const string& text); static inline void HEAD3(const string& text); static inline void END(); static inline void CUT(); ostream& operator<<(ostream& out, const vector<string>& vec); ostream& operator<<(ostream& out, const Book& book); ostream& operator<<(ostream& out, const map<string, Book>& books); void testStdMap(); void runClient(LookUpTable* ptr); int main() { BANNER(); CUT(); HEAD1("Test std::map"); testStdMap(); CUT(); HEAD1("Test class AdapterClass"); AdapterClass ac; runClient(&ac); CUT(); HEAD1("Test class AdapterObject"); AdapterObject ao; runClient(&ao); CUT(); cout << "Press enter to continue..." << endl; std::cin.get(); // Portable system("pause") } /* Test Cases */ void runClient(LookUpTable* ptr) { Book book1("C++ Primer", { "Stanley Lippman", "Josée Lajoie", "Barbara E. Moo" }, 40.94f); Book book2("Modern Fortran: Building efficient parallel applications", { "Milan Curcic" }, 59.99f); Book book3("C++ Programming Language (hardcover), The 4th Edition", { "Bjarne Stroustrup" }, 70.63f); // insert HEAD2("LookUpTable Interface: insert"); { cout << ptr->insert(book1) << endl; // Ans: 1 cout << ptr->insert(book2) << endl; // Ans: 1 cout << ptr->insert(book3) << endl; // Ans: 1 cout << ptr->insert(book1) << endl; // Ans: 0 cout << ptr->insert(book3) << endl; // Ans: 0 } END(); // remove HEAD2("LookUpTable Interface: remove"); { cout << ptr->remove(book2) << endl; // Ans: 1 cout << ptr->remove(book2) << endl; // Ans: 0 } END(); // get HEAD2("LookUpTable Interface: get"); { cout << ptr->get(book1.name()) << endl; // Ans: <memory address> cout << ptr->get(book2.name()) << endl; // Ans: 0 cout << ptr->get(book3.name()) << endl; // Ans: <memory address> } END(); } void testStdMap() { // Key: std::string, Value: Book map<string, Book> mapbook; // insert HEAD2("Test Map: Insert"); { string key = "C++ Primer"; Book val("C++ Primer", { "Stanley Lippman", "Josée Lajoie", "Barbara E. Moo" }, 40.94f); bool success = false; std::tie(std::ignore, success) = mapbook.insert(pair<string, Book>(key, val)); if (success) { cout << "Inserted " << val << " into the map." << endl; } key = "Modern Fortran: Building efficient parallel applications"; val.assign(key, { "Milan Curcic" }, 59.99f); std::tie(std::ignore, success) = mapbook.emplace(key, val); if (success) { cout << "Inserted " << val << " into the map." << endl; } // Bjarne Stroustrup is a Danish computer scientist, most notable for the // invention and development of the C++ programming language. key = "C++ Programming Language (hardcover), The 4th Edition"; val.assign(key, { "Bjarne Stroustrup" }, 70.63f); std::tie(std::ignore, success) = mapbook.emplace(key, val); if (success) { cout << "Inserted " << val << " into the map." << endl; } } END(); HEAD2("Test Map: Print"); { HEAD3("Key-Value"); for (auto it = mapbook.begin(); it != mapbook.end(); it++) { cout << "" << quoted(it->first) << " -> " << it->second << endl; } END(); HEAD3("JSON"); cout << mapbook << endl; END(); } END(); HEAD2("Test Map: Find"); { string key = "Modern Fortran: Building efficient parallel applications"; if (mapbook.find(key) == mapbook.end()) { cout << "Could not find " << quoted(key) << "." << endl; } else { cout << "Found " << quoted(key) << "." << endl; } key = "Modern Fortran"; if (mapbook.find(key) == mapbook.end()) { cout << "Could not find " << quoted(key) << "." << endl; } else { cout << "Found " << quoted(key) << "." << endl; } } END(); HEAD2("Test Map: Find and Erase"); { string key = "Modern Fortran: Building efficient parallel applications"; auto it = mapbook.find("Modern Fortran: Building efficient parallel applications"); if (it == mapbook.end()) { cout << "Could not find " << quoted(key) << "." << endl; } else { cout << "Erased " << quoted(key) << "." << endl; mapbook.erase(it); } key = "Modern Fortran: Building efficient parallel applications"; it = mapbook.find(key); if (it == mapbook.end()) { cout << "Could not find " << quoted(key) << "." << endl; } else { cout << "Erased " << quoted(key) << "." << endl; mapbook.erase(it); } } END(); cout << endl; } ostream& operator<<(ostream& out, const vector<string>& vec) { ostringstream vectorStream; string separator = ""; vectorStream << "["; for (auto it = vec.begin(); it != vec.end(); ++it) { vectorStream << separator << quoted(*it); separator = ", "; } vectorStream << "]"; return (out << vectorStream.str()); } ostream& operator<<(ostream& out, const Book& book) { ostringstream bookStream; bookStream << "{" << quoted(book.m_name) << ", " << book.m_authors << ", " << book.m_price << "}"; return (out << bookStream.str()); } ostream& operator<<(ostream& out, const map<string, Book>& books) { ostringstream booksJSON; string separator = ""; const string indent = " "; booksJSON << "{\n"; for (auto it = books.begin(); it != books.end(); ++it) { booksJSON << separator << indent << quoted(it->first) << ": {\n" << indent << indent << quoted("name") << ": " << quoted(it->second.name()) << ",\n" << indent << indent << quoted("authors") << ": " << it->second.authors() << ",\n" << indent << indent << quoted("price") << ": " << it->second.price() << "\n" << indent << "}"; separator = ",\n"; } booksJSON << "\n}" << endl; return (out << booksJSON.str()); } void BANNER() { cout << R"AdapterPattern( ________ ______ ________ ______ _________ ______ ______ /_______/\ /_____/\ /_______/\ /_____/\ /________/\/_____/\ /_____/\ \::: _ \ \\:::_ \ \\::: _ \ \\:::_ \ \\__.::.__\/\::::_\/_\:::_ \ \ \::(_) \ \\:\ \ \ \\::(_) \ \\:(_) \ \ \::\ \ \:\/___/\\:(_) ) )_ \:: __ \ \\:\ \ \ \\:: __ \ \\: ___\/ \::\ \ \::___\/_\: __ `\ \ \:.\ \ \ \\:\/.:| |\:.\ \ \ \\ \ \ \::\ \ \:\____/\\ \ `\ \ \ \__\/\__\/ \____/_/ \__\/\__\/ \_\/ \__\/ \_____\/ \_\/ \_\/ ______ ________ _________ _________ ______ ______ ___ __ /_____/\ /_______/\ /________/\/________/\/_____/\ /_____/\ /__/\ /__/\ \:::_ \ \\::: _ \ \\__.::.__\/\__.::.__\/\::::_\/_\:::_ \ \ \::\_\\ \ \ \:(_) \ \\::(_) \ \ \::\ \ \::\ \ \:\/___/\\:(_) ) )_\:. `-\ \ \ \: ___\/ \:: __ \ \ \::\ \ \::\ \ \::___\/_\: __ `\ \\:. _ \ \ \ \ \ \:.\ \ \ \ \::\ \ \::\ \ \:\____/\\ \ `\ \ \\. \`-\ \ \ \_\/ \__\/\__\/ \__\/ \__\/ \_____\/ \_\/ \_\/ \__\/ \__\/ )AdapterPattern"; } void HEAD1(const string& text) { string tildes(text.length(), '~'); cout << tildes << "\n"; cout << text << "\n"; cout << tildes << "\n\n"; } static inline void _HEAD_INTERNAL(const string& text, char ch) { string line(text.length(), ch); cout << text << "\n"; cout << line << "\n\n"; } void HEAD2(const string& text) { _HEAD_INTERNAL(text, '^'); } void HEAD3(const string& text) { _HEAD_INTERNAL(text, '*'); } void END() { cout << endl; } void CUT() { string line(80, '='); cout << line << "\n" << endl; }

Source/CMakeLists.txt

add_executable(adapter) # 新增 include 路徑 # #include 指令中的相對路徑會從專案根目錄之下的 "Source" 資料夾開始計算 target_include_directories(adapter PRIVATE "${CMAKE_SOURCE_DIR}/Source") target_sources(adapter PRIVATE "Adapter/AdapterClass.cpp" "Adapter/AdapterObject.cpp" "Book.cpp" "Service/Adaptee.cpp" "main.cpp" )

Source/Adapter/AdapterClass.cpp

#include "Adapter/AdapterClass.hpp" using std::string; int AdapterClass::insert(const Book& book) { // TODO Call methods of class Adaptee return 0; } int AdapterClass::remove(const Book& book) { // TODO Call methods of class Adaptee return 0; } Book* AdapterClass::get(const string& bookName) { // TODO Call methods of class Adaptee return nullptr; }

Source/Adapter/AdapterClass.hpp

#ifndef ADAPTERCLASS_H_ #define ADAPTERCLASS_H_ #include "Book.hpp" #include "ClientInterface/LookUpTable.hpp" #include "Service/Adaptee.hpp" class AdapterClass : public LookUpTable, public Adaptee { public: int insert(const Book& book) override; int remove(const Book& book) override; Book* get(const std::string& bookName) override; }; #endif // ADAPTERCLASS_H_

Source/Adapter/AdapterObject.cpp

#include "Adapter/AdapterObject.hpp" using std::string; int AdapterObject::insert(const Book& book) { // TODO Call methods of class Adaptee return 0; } int AdapterObject::remove(const Book& book) { // TODO Call methods of class Adaptee return 0; } Book* AdapterObject::get(const string& bookName) { // TODO Call methods of class Adaptee return nullptr; }

Source/Adapter/AdapterObject.hpp

#ifndef ADAPTEROBJECT_H_ #define ADAPTEROBJECT_H_ #include <string> #include "Book.hpp" #include "ClientInterface/LookUpTable.hpp" #include "Service/Adaptee.hpp" class AdapterObject : public LookUpTable { private: Adaptee ad; public: int insert(const Book& book) override; int remove(const Book& book) override; Book* get(const std::string& bookName) override; }; #endif // ADAPTEROBJECT_H_

Source/ClientInterface/LookUpTable.hpp

#ifndef LOOKUPTABLE_H_ #define LOOKUPTABLE_H_ #include <string> #include "Book.hpp" // Target Class class LookUpTable { public: /** 加入一個值。成功回傳 1;失敗回傳 0。 */ virtual int insert(const Book&) = 0; /** 刪除一個值。成功回傳 1;失敗回傳 0。 */ virtual int remove(const Book&) = 0; /** 查詢一個值 - 成功:回傳一個 Book 指標指向查詢到的 Book。 - 失敗:回傳 nullptr。 */ virtual Book* get(const std::string&) = 0; }; #endif // LOOKUPTABLE_H_

Source/Service/Adaptee.cpp

#include "Service/Adaptee.hpp" // void Adaptee::insert(const Book& book, int& result) {} // void Adaptee::remove(const Book& book, int& result) {} // void Adaptee::get(const std::string& bookName, Book*& ptr) {}

Source/Service/Adaptee.hpp

#ifndef ADAPTEE_H_ #define ADAPTEE_H_ #include <map> #include <string> #include "Book.hpp" class Adaptee { private: std::map<std::string, Book> mapBook; public: /** 加入一個值 成功 result 設成 1;失敗 result 設成 0。 */ void insert(const Book& boo, int& result); /** 刪除一個值 成功 result 設成 1;失敗 result 設成 0。 */ void remove(const Book& book, int& result); /** 查詢一個值 - 成功:指標 ptr 指到 table 的值。 - 失敗:指標 ptr 設成 0。 */ void get(const std::string& bookName, Book*& ptr); }; #endif // ADAPTEE_H_