การจัดการพารามิเตอร์ในแอปพลิเคชัน OOP


15

ฉันกำลังเขียนแอปพลิเคชัน OOP ขนาดกลางใน C ++ เป็นวิธีฝึกหลักการ OOP

ฉันมีหลายคลาสในโครงการของฉันและบางส่วนจำเป็นต้องเข้าถึงพารามิเตอร์การกำหนดค่าใช้งาน พารามิเตอร์เหล่านี้อ่านจากแหล่งต่าง ๆ ในระหว่างการเริ่มต้นแอปพลิเคชัน บางไฟล์ถูกอ่านจากไฟล์ปรับแต่งในผู้ใช้ที่บ้านบางคนเป็นอาร์กิวเมนต์บรรทัดคำสั่ง (argv)

ConfigBlockดังนั้นผมจึงสร้างชั้นเรียน คลาสนี้อ่านแหล่งพารามิเตอร์ทั้งหมดและเก็บไว้ในโครงสร้างข้อมูลที่เหมาะสม ตัวอย่างคือชื่อพา ธ และชื่อไฟล์ที่ผู้ใช้สามารถเปลี่ยนได้ในไฟล์กำหนดค่าหรือ - --verbose CLI flag จากนั้นหนึ่งสามารถโทรConfigBlock.GetVerboseLevel()เพื่ออ่านพารามิเตอร์เฉพาะนี้

คำถามของฉัน: เป็นการดีหรือไม่ที่จะรวบรวมข้อมูลการกำหนดค่ารันไทม์ทั้งหมดในชั้นเดียว?

จากนั้นชั้นเรียนของฉันต้องการเข้าถึงพารามิเตอร์เหล่านี้ทั้งหมด ฉันสามารถคิดหลายวิธีในการบรรลุเป้าหมายนี้ แต่ฉันไม่แน่ใจว่าควรใช้วิธีใด คอนสตรัคเตอร์ของคลาสสามารถอ้างอิงถึง ConfigBlock ของฉันได้

public:
    MyGreatClass(ConfigBlock &config);

หรือพวกเขาเพียงแค่รวมส่วนหัว "CodingBlock.h" ซึ่งมีคำจำกัดความของ CodingBlock ของฉัน:

extern CodingBlock MyCodingBlock;

จากนั้นไฟล์คลาส. cpp เท่านั้นที่ต้องรวมและใช้สิ่งที่ ConfigBlock
ไฟล์. h ไม่แนะนำอินเทอร์เฟซนี้กับผู้ใช้ของคลาส อย่างไรก็ตามอินเทอร์เฟซสำหรับ ConfigBlock ยังคงอยู่อย่างไรก็ตามมันถูกซ่อนจากไฟล์. h

เป็นการดีที่จะซ่อนมันด้วยวิธีนี้หรือไม่?

ฉันต้องการให้อินเทอร์เฟซมีขนาดเล็กที่สุดเท่าที่จะเป็นไปได้ แต่สุดท้ายแล้วฉันเดาว่าทุกคลาสที่ต้องการพารามิเตอร์การตั้งค่าจะต้องมีการเชื่อมต่อกับ ConfigBlock ของฉัน แต่การเชื่อมต่อนี้ควรมีลักษณะอย่างไร

คำตอบ:


10

ฉันเป็นนักปฏิบัตินิยม แต่ความกังวลหลักของฉันที่นี่คือคุณอาจยอมให้สิ่งนี้ConfigBlockครอบงำการออกแบบส่วนต่อประสานของคุณในทางที่ไม่ดี เมื่อคุณมีสิ่งนี้:

explicit MyGreatClass(const ConfigBlock& config);

... อินเทอร์เฟซที่เหมาะสมกว่าอาจเป็นดังนี้:

MyGreatClass(int foo, float bar, const string& baz);

... ตรงข้ามกับการหยิบเชอร์รี่เหล่านี้ foo/bar/bazConfigBlockเขตข้อมูลจากขนาดใหญ่

การออกแบบส่วนต่อประสานที่ขี้เกียจ

ในด้านบวกการออกแบบชนิดนี้ทำให้ง่ายต่อการออกแบบส่วนต่อประสานที่มีความเสถียรสำหรับตัวสร้างของคุณเช่นถ้าคุณต้องการสิ่งใหม่คุณสามารถโหลดมันลงใน ConfigBlock (อาจไม่มีการเปลี่ยนแปลงรหัส) เลือกสิ่งใดใหม่ที่คุณต้องการโดยไม่ต้องชนิดของการเปลี่ยนแปลงอินเตอร์เฟซใด ๆ MyGreatClassเพียงการเปลี่ยนแปลงการดำเนินการตามที่

ดังนั้นทั้งมืออาชีพและมืออาชีพที่ทำให้คุณมีอิสระในการออกแบบอินเทอร์เฟซที่คำนึงถึงอย่างรอบคอบมากขึ้นซึ่งยอมรับเฉพาะอินพุตที่ต้องการจริงๆเท่านั้น มันใช้ความคิดของ"เพียงแค่ให้ฉันหยดของข้อมูลขนาดใหญ่นี้ฉันจะเลือกสิ่งที่ฉันต้องการจากมัน"เมื่อเทียบกับสิ่งที่ชอบ "พารามิเตอร์ที่แม่นยำเหล่านี้คือสิ่งที่ส่วนต่อประสานนี้ต้องใช้งาน"

ดังนั้นจึงมีข้อดีบางอย่างที่นี่ แต่พวกเขาอาจจะมีน้ำหนักเกินกว่าข้อเสีย

การมีเพศสัมพันธ์

ในสถานการณ์นี้คลาสดังกล่าวทั้งหมดจะถูกสร้างขึ้นจากConfigBlockอินสแตนซ์ท้ายมีการอ้างอิงของพวกเขามีลักษณะเช่นนี้:

ป้อนคำอธิบายรูปภาพที่นี่

สิ่งนี้สามารถกลายเป็น PITA ตัวอย่างเช่นถ้าคุณต้องการทดสอบหน่วยClass2ในแผนภาพนี้โดยแยก คุณอาจจะต้องจำลองแบบต่าง ๆ อย่างพิถีพิถันConfigBlockอินพุตที่มีเขตข้อมูลที่เกี่ยวข้องClass2สนใจอย่างพิถีพิถันเพื่อให้สามารถทดสอบได้ภายใต้เงื่อนไขที่หลากหลาย

ในบริบทใหม่ประเภทใด ๆ (ไม่ว่าจะเป็นการทดสอบหน่วยหรือโครงการใหม่ทั้งหมด) คลาสใด ๆ ดังกล่าวสามารถกลายเป็นภาระในการใช้งานได้มากขึ้นในขณะที่เราท้ายที่สุดต้องนำมาด้วยเสมอConfigBlockสำหรับการขี่และตั้งค่า ตาม

สามารถนำมาใช้ / deployability / การตรวจสอบได้

แต่ถ้าคุณออกแบบอินเทอร์เฟซเหล่านี้อย่างเหมาะสมเราสามารถแยกออกจากConfigBlockและจบลงด้วยสิ่งนี้:

ป้อนคำอธิบายรูปภาพที่นี่

หากคุณสังเกตเห็นในแผนภาพข้างต้นคลาสทั้งหมดจะแยกจากกัน (ข้อต่อส่วนต่อ / ขาออกลดลง 1)

สิ่งนี้นำไปสู่คลาสที่เป็นอิสระมากขึ้น (อย่างน้อยก็เป็นอิสระจาก ConfigBlock ) ซึ่งสามารถใช้ / ทดสอบได้ง่ายขึ้นในสถานการณ์ / โครงการใหม่

ตอนนี้Clientรหัสนี้กลายเป็นสิ่งที่ต้องพึ่งพาทุกสิ่งและรวมเข้าด้วยกัน ภาระจะถูกโอนไปยังรหัสลูกค้านี้เพื่ออ่านฟิลด์ที่เหมาะสมจากConfigBlockและส่งไปยังคลาสที่เหมาะสมเป็นพารามิเตอร์ แต่โดยทั่วไปแล้วรหัสลูกค้านั้นได้รับการออกแบบมาอย่างแคบสำหรับบริบทที่เฉพาะเจาะจงและศักยภาพในการใช้ซ้ำนั้นโดยทั่วไปจะเป็น zilch หรือปิดต่อไป (อาจเป็นแอปพลิเคชันของคุณmainฟังก์ชันจุดเข้าคุณ

ดังนั้นจากความสามารถในการนำกลับมาใช้ใหม่และการทดสอบจุดยืนมันสามารถช่วยให้ชั้นเรียนเหล่านี้เป็นอิสระมากขึ้น จากมุมมองของอินเทอร์เฟซสำหรับผู้ที่ใช้คลาสของคุณมันยังสามารถช่วยระบุพารามิเตอร์ที่พวกเขาต้องการได้อย่างชัดเจนแทนที่จะมีขนาดใหญ่เพียงอันเดียวConfigBlockซึ่งจำลองจักรวาลทั้งหมดของเขตข้อมูลที่จำเป็นสำหรับทุกสิ่ง

ข้อสรุป

โดยทั่วไปแล้วการออกแบบในระดับนี้จะขึ้นอยู่กับหินใหญ่ก้อนเดียวซึ่งมีทุกสิ่งที่จำเป็นต้องมีลักษณะเหล่านี้ การบังคับใช้การปรับใช้การใช้ซ้ำได้การทดสอบ ฯลฯ สามารถลดลงอย่างมีนัยสำคัญ แต่พวกเขาสามารถทำให้การออกแบบส่วนต่อประสานง่ายขึ้นหากเราพยายามหมุนเป็นบวก มันขึ้นอยู่กับคุณที่จะวัดข้อดีและข้อเสียเหล่านั้นและตัดสินใจว่าการแลกเปลี่ยนนั้นคุ้มค่าหรือไม่ โดยทั่วไปแล้วจะปลอดภัยกว่าที่จะผิดกับการออกแบบประเภทนี้ที่คุณหยิบเชอร์รี่จากหินใหญ่ก้อนเดียวในชั้นเรียนที่มีจุดประสงค์เพื่อสร้างแบบจำลองทั่วไปที่กว้างกว่า

สุดท้าย แต่ไม่ท้ายสุด:

extern CodingBlock MyCodingBlock;

... นี่อาจเป็นยิ่งแย่ลง (เบ้มากขึ้น?) ในแง่ของลักษณะที่อธิบายข้างต้นกว่าวิธีการฉีดพึ่งพาในขณะที่มันจบลงด้วยการมีเพศสัมพันธ์ชั้นเรียนของคุณไม่เพียง แต่จะConfigBlocksแต่โดยตรงกับกรณีเฉพาะของมัน นั่นจะทำให้ความสามารถในการปรับใช้

คำแนะนำทั่วไปของฉันจะทำผิดพลาดในด้านของการออกแบบอินเตอร์เฟสซึ่งไม่ขึ้นอยู่กับ monoliths ประเภทนี้เพื่อให้พารามิเตอร์ของพวกเขาอย่างน้อยที่สุดสำหรับคลาสที่ใช้งานได้โดยทั่วไปที่คุณออกแบบ และหลีกเลี่ยงวิธีการทั่วโลกโดยไม่ต้องพึ่งพาการฉีดถ้าคุณทำได้เว้นแต่คุณจะมีเหตุผลที่แข็งแกร่งและมั่นใจมากที่จะไม่หลีกเลี่ยง


1

โดยปกติแล้วการกำหนดค่าแอปพลิเคชันจะถูกใช้โดยวัตถุจากโรงงานเป็นหลัก วัตถุใด ๆ ที่ใช้การกำหนดค่าควรสร้างจากวัตถุหนึ่งในโรงงานเหล่านั้น คุณสามารถใช้รูปแบบนามธรรมจากโรงงานเพื่อนำคลาสหนึ่งที่ใช้ในConfigBlockวัตถุทั้งหมด คลาสนี้จะเปิดเผยวิธีการสาธารณะเพื่อส่งคืนวัตถุโรงงานอื่น ๆ และจะส่งผ่านเฉพาะในส่วนที่ConfigBlockเกี่ยวข้องกับวัตถุโรงงานนั้นเท่านั้น วิธีการตั้งค่าการตั้งค่า "หยดลง"ConfigBlockวัตถุไปยังสมาชิกและจากโรงงานจากโรงงานไปยังโรงงาน

ฉันจะใช้ C # เนื่องจากฉันรู้ภาษาดีกว่า แต่สิ่งนี้ควรจะสามารถเปลี่ยนเป็น C ++ ได้อย่างง่ายดาย

public class ConfigBlock
{
    public ConfigBlock()
    {
        // Load config data and
        // connectionSettings = new ConnectionConfig();
        // connectionSettings...
    }

    private ConnectionConfig connectionSettings;

    public ConnectionConfig GetConnectionSettings()
    {
        return connectionSettings;
    }
}

public class FactoryProvider
{
    public FactoryProvider(ConfigBlock config)
    {
        this.config = config;
    }

    private ConfigBlock config;

    public ConnectionFactory GetConnectionFactory()
    {
        ConnectionConfig connectionSettings = config.GetConnectionSettings();

        return new ConnectionFactory(connectionSettings);
    }
}

public class ConnectionFactory
{
    public ConnectionFactory(ConnectionConfig settings)
    {
        this.settings = settings;
    }

    private ConnectionConfig settings;

    public Connection GetConnection()
    {
        return new Connection(settings.Hostname, settings.Port, settings.Username, settings.Password);
    }
}

หลังจากนั้นคุณต้องมีคลาสที่ทำหน้าที่เป็น "แอปพลิเคชัน" ที่รับอินสแตนซ์ในโพรซีเดอร์หลักของคุณ:

// Your main procedure (yeah I'm bending the rules of C# a tad here,
// but you get the point).
int Main(string[] args)
{
    Application app = new Application();

    app.Run();
}

public class Application
{
    public Application()
    {
        config = new ConfigBlock();
        factoryProvider = new FactoryProvider(config);
    }

    private ConfigBlock config;
    private FactoryProvider factoryProvider;

    public void Run()
    {
        ConnectionFactory connections = factoryProvider.GetConnectionFactory();
        Connection connection = connections.GetConnection();

        connection.Connect();

        // Enter into your main loop and do what this program is meant to do
    }
}

เป็นหนึ่งในบันทึกย่อล่าสุดนี้เป็นที่รู้จักกันในชื่อ "ผู้ให้บริการวัตถุ" ใน. NET พูด วัตถุผู้ให้บริการใน. NET ดูเหมือนจะแต่งงานกับข้อมูลการกำหนดค่าไปยังวัตถุในโรงงานซึ่งเป็นสิ่งที่คุณต้องการทำที่นี่

ดูรูปแบบผู้ให้บริการสำหรับผู้เริ่มต้นด้วย อีกครั้งนี่คือการมุ่งสู่การพัฒนา. NET แต่ด้วย C # และ C ++ ทั้งสองเป็นภาษาเชิงวัตถุรูปแบบที่ควรจะถ่ายโอนส่วนใหญ่ระหว่างทั้งสอง

ที่ดีอีกอ่านที่เกี่ยวข้องกับรูปแบบนี้: ผู้ให้บริการรุ่น

สุดท้ายบทวิจารณ์ของรูปแบบนี้: ผู้ให้บริการไม่ใช่รูปแบบ


ทุกอย่างดียกเว้นลิงค์ไปยังรุ่นผู้ให้บริการ การสะท้อนกลับไม่ได้รับการสนับสนุนโดย c ++ และนั่นจะไม่ทำงาน
BЈовић

@ BЈовић: ถูกต้อง การสะท้อนคลาสไม่มีอยู่อย่างไรก็ตามคุณสามารถสร้างในวิธีแก้ปัญหาแบบแมนนวลซึ่งโดยทั่วไปจะเปลี่ยนเป็นswitchคำสั่งหรือการifทดสอบข้อความเทียบกับค่าที่อ่านจากไฟล์ปรับแต่ง
Greg Burghardt

0

คำถามแรก: เป็นการดีหรือไม่ที่จะรวบรวมข้อมูลการกำหนดค่ารันไทม์ทั้งหมดในชั้นเดียว?

ใช่. มันจะดีกว่าที่จะรวมศูนย์ค่าคงที่และค่ารันไทม์และรหัสให้อ่าน

คอนสตรัคเตอร์ของคลาสสามารถอ้างอิงให้กับ ConfigBlock ของฉันได้

สิ่งนี้ไม่ดี: ตัวสร้างส่วนใหญ่ของคุณจะไม่ต้องการค่าส่วนใหญ่ สร้างอินเทอร์เฟซสำหรับทุกสิ่งที่ไม่น่าสร้าง

รหัสเก่า (ข้อเสนอของคุณ):

MyGreatClass(ConfigBlock &config);

รหัสใหม่:

struct GreatClassData {/*...*/}; // initialization data for MyGreatClass
GreatClassData ConfigBlock::great_class_values();

ยกตัวอย่าง MyGreatClass:

auto x = MyGreatClass{ current_config_block.great_class_values() };

ที่นี่ current_config_blockคือตัวอย่างของConfigBlockคลาสของคุณ(อันที่มีค่าทั้งหมดของคุณ) และMyGreatClassคลาสได้รับGreatClassDataอินสแตนซ์ กล่าวอีกนัยหนึ่งส่งต่อไปยังผู้สร้างข้อมูลที่ต้องการและเพิ่มสิ่งอำนวยความสะดวกให้กับคุณConfigBlockเพื่อสร้างข้อมูลนั้น

หรือพวกเขาเพียงแค่รวมส่วนหัว "CodingBlock.h" ซึ่งมีคำจำกัดความของ CodingBlock ของฉัน:

 extern CodingBlock MyCodingBlock;

จากนั้นไฟล์คลาส. cpp เท่านั้นที่ต้องรวมและใช้สิ่งที่ ConfigBlock ไฟล์. h ไม่แนะนำอินเทอร์เฟซนี้กับผู้ใช้ของคลาส อย่างไรก็ตามอินเทอร์เฟซสำหรับ ConfigBlock ยังคงอยู่อย่างไรก็ตามมันถูกซ่อนจากไฟล์. h เป็นการดีที่จะซ่อนมันด้วยวิธีนี้หรือไม่?

รหัสนี้แสดงว่าคุณจะมีอินสแตนซ์ CodingBlock ทั่วโลก อย่าทำอย่างนั้น: โดยปกติคุณควรมีอินสแตนซ์ที่ประกาศไปทั่วโลกไม่ว่าจะเป็นจุดเริ่มต้นที่แอปพลิเคชันของคุณใช้ (ฟังก์ชั่นหลัก, DllMain, ฯลฯ ) และส่งผ่านสิ่งนั้นรอบ ๆ ทั้งคลาสที่อยู่รอบ ๆ เพียงแค่แสดงอินเตอร์เฟสรอบ ๆ ข้อมูลและส่งต่อสิ่งเหล่านั้น)

นอกจากนี้อย่าผูกคลาสลูกค้าของคุณ (ของคุณMyGreatClass) กับประเภทของCodingBlock; ซึ่งหมายความว่าถ้าคุณใช้เวลาในสตริงและห้าจำนวนเต็มคุณจะดีกว่าที่ผ่านในสตริงที่และจำนวนเต็มกว่าที่คุณจะได้รับการผ่านในMyGreatClassCodingBlock


ฉันคิดว่ามันเป็นความคิดที่ดีที่จะแยกโรงงานออกจากการกำหนดค่า เป็นเรื่องที่น่าพอใจที่การติดตั้งใช้งานควรรู้วิธีสร้างอินสแตนซ์ของส่วนประกอบเนื่องจากจำเป็นต้องมีการพึ่งพา 2 ทางซึ่งก่อนหน้านี้มีเพียงการพึ่งพา 1 ทางเท่านั้น สิ่งนี้มีผลกระทบอย่างมากเมื่อขยายรหัสของคุณโดยเฉพาะเมื่อใช้ไลบรารีที่แบ่งใช้ซึ่งอินเทอร์เฟซมีความสำคัญจริงๆ
Joel Cornett

0

คำตอบสั้น ๆ :

คุณไม่จำเป็นต้องตั้งค่าทั้งหมดสำหรับแต่ละโมดูล / คลาสในรหัสของคุณ ถ้าคุณทำแล้วมีบางอย่างผิดปกติกับการออกแบบเชิงวัตถุของคุณ โดยเฉพาะอย่างยิ่งในกรณีของการทดสอบหน่วยการตั้งค่าตัวแปรทั้งหมดที่คุณไม่ต้องการและผ่านวัตถุนั้นจะไม่ช่วยในการอ่านหรือการบำรุงรักษา


วิธีนี้ฉันสามารถรวบรวมรหัส parser (บรรทัดคำสั่งแยกวิเคราะห์และไฟล์ config) ในตำแหน่งกลาง จากนั้นแต่ละชั้นเรียนสามารถเลือกพารามิเตอร์ที่เกี่ยวข้องได้จากที่นั่น การออกแบบที่ดีในความคิดของคุณคืออะไร?
lugge86

บางทีฉันอาจจะเขียนผิด - ฉันหมายความว่าคุณมี (และเป็นวิธีปฏิบัติที่ดี) เพื่อให้มีนามธรรมโดยทั่วไปด้วยการตั้งค่าทั้งหมดที่ได้รับจากตัวแปรไฟล์ / ตัวแปรสภาพแวดล้อม - ซึ่งอาจเป็นConfigBlockคลาสของคุณ จุดนี่คือการไม่ให้ทั้งหมดในกรณีนี้บริบทของสถานะของระบบโดยเฉพาะอย่างยิ่งค่าที่จำเป็นในการทำเช่นนั้น
Dawid Pura
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.