การใช้อินเทอร์เฟซสำหรับรหัสคู่ที่หลวม


10

พื้นหลัง

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

  1. กำหนดอินเตอร์เฟซใน C # IDeviceโครงการหนึ่ง
  2. มีรูปธรรมในห้องสมุดที่กำหนดไว้ในโครงการ C # อื่นที่จะใช้เพื่อเป็นตัวแทนของอุปกรณ์
  3. มีอุปกรณ์ที่เป็นรูปธรรมใช้IDeviceอินเตอร์เฟซ
  4. IDeviceอินเตอร์เฟซที่อาจจะมีวิธีการเช่นหรือGetMeasurementSetRange
  5. ทำให้แอปพลิเคชันมีความรู้เกี่ยวกับรูปธรรมและส่งผ่านรูปธรรมไปยังรหัสแอปพลิเคชั่นที่ใช้งาน ( ไม่นำไปใช้ ) IDeviceอุปกรณ์

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

ข้อสงสัยเดียวในใจของฉันคือตอนนี้ทั้งแอปพลิเคชันและคลาสที่เป็นรูปธรรมของอุปกรณ์นั้นขึ้นอยู่กับไลบรารีที่มีIDeviceส่วนต่อประสาน แต่นั่นเป็นสิ่งที่ไม่ดี?

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

คำถาม

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


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

@ Kilian ทั้งที่ฉันมีความรู้สึกลืมที่จะเพิ่มส่วนหนึ่งในคำถามของฉัน ดู # 5
Snoop

คำตอบ:


5

ฉันคิดว่าคุณมีความเข้าใจที่ดีเกี่ยวกับการทำงานของซอฟต์แวร์แยกส่วน :)

ข้อสงสัยเดียวในใจของฉันคือตอนนี้ทั้งแอปพลิเคชั่นและคลาสที่เป็นรูปธรรมของอุปกรณ์นั้นขึ้นอยู่กับไลบรารีที่มีส่วนต่อประสาน IDevice แต่นั่นเป็นสิ่งที่ไม่ดี?

ไม่จำเป็นต้องเป็น!

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

คุณสามารถจัดการกับทุกความกังวลเหล่านี้มีโครงสร้างโครงการ

วิธีที่ฉันทำตามปกติ:

  • นำสิ่งนามธรรมของฉันทั้งหมดในCommonโครงการ MyBiz.Project.Commonสิ่งที่ชอบ โครงการอื่น ๆ มีอิสระในการอ้างอิง แต่อาจไม่อ้างอิงโครงการอื่น
  • เมื่อฉันสร้างการนำนามธรรมมาใช้อย่างเป็นรูปธรรมฉันวางมันลงในโครงการแยกต่างหาก MyBiz.Project.Devices.TemperatureSensorsสิ่งที่ชอบ โครงการนี้จะอ้างอิงCommonโครงการ
  • ฉันมีClientโครงการของฉันซึ่งเป็นจุดเริ่มต้นของการสมัคร (คล้ายMyBiz.Project.Desktop) เมื่อเริ่มต้นแอปพลิเคชันจะเข้าสู่กระบวนการBootstrappingซึ่งฉันกำหนดค่าการแมปการวางแนว / นามธรรม ฉันจะยกตัวอย่างที่เป็นรูปธรรมของฉันIDevicesชอบWaterTemperatureSensorและIRCameraTemperatureSensorที่นี่หรือฉันจะกำหนดค่าบริการเช่นโรงงานหรือภาชนะ IoC จะยกตัวอย่างประเภทคอนกรีตที่เหมาะสมสำหรับฉันในภายหลัง

สิ่งสำคัญในที่นี้คือมีเพียงClientโครงการของคุณเท่านั้นที่ต้องระวังทั้งCommonโครงการนามธรรมและโครงการติดตั้งที่เป็นรูปธรรมทั้งหมด ด้วยการ จำกัด การแม็พ abstract-> คอนกรีตกับโค้ด Bootstrap ของคุณคุณจะสามารถทำให้แอปพลิเคชั่นที่เหลือของคุณไม่รู้ถึงประเภทคอนกรีต

รหัสที่มาคู่อย่างอิสระ ftw :)


2
DI ทำตามธรรมชาติจากที่นี่ ส่วนใหญ่เป็นปัญหาเกี่ยวกับโครงสร้างโครงการ / รหัสเช่นกัน การมีคอนเทนเนอร์ IoC สามารถช่วย DI ได้ แต่ไม่ใช่ข้อกำหนดเบื้องต้น คุณสามารถบรรลุ DI โดยการฉีดการพึ่งพาตนเองเช่นกัน!
MetaFight

1
@StevieV ใช่ถ้าวัตถุ Foo ในแอปพลิเคชันของคุณต้องการ IDevice การพึ่งพาการพึ่งพาสามารถทำได้ง่ายเพียงแค่ฉีดผ่าน Constructor ( new Foo(new DeviceA());) แทนที่จะมีฟิลด์ส่วนตัวภายใน Foo สร้างอินสแตนซ์ DeviceA เอง ( private IDevice idevice = new DeviceA();) - คุณยังคงบรรลุ DI เช่น Foo ไม่รู้จัก DeviceA ในกรณีแรก
อีก

2
@ อีกแล้วบันทึกของคุณและการป้อนข้อมูล MetaFight มีประโยชน์มาก
Snoop

1
@StevieV เมื่อคุณมีเวลานี่เป็นคำแนะนำที่ดีสำหรับ Dependency Inversion ในฐานะแนวคิด (จาก "ลุง Bob" Martin คนที่เป็นคนกำหนดค่า) ซึ่งแตกต่างจาก Inversion of Control / Depency Injection framework เช่น Spring (หรืออื่น ๆ อีกมากมาย ตัวอย่างที่เป็นที่นิยมในโลกC♯ :))
2559

1
@ anotherdave วางแผนที่จะตรวจสอบเอาไว้อย่างแน่นอนขอบคุณอีกครั้ง
Snoop

3

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

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

แก้ไข

ในการจัดการข้อกังวลที่ห้าของคุณให้นึกถึงโครงสร้างเช่นนี้ (ฉันถือว่าคุณเป็นผู้ควบคุมการกำหนดอุปกรณ์ของคุณ):

คุณมีห้องสมุดหลัก ในนั้นเป็นอินเตอร์เฟซที่เรียกว่า IDevice

ในไลบรารีอุปกรณ์ของคุณคุณมีการอ้างอิงถึงไลบรารีหลักของคุณและคุณได้กำหนดชุดของอุปกรณ์ที่ใช้ IDevice ทั้งหมด คุณมีโรงงานที่รู้วิธีสร้าง IDevices ประเภทต่างๆ

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

นี่เป็นวิธีที่เป็นไปได้หลายวิธีในการจัดการกับข้อกังวลของคุณ

ตัวอย่าง:

namespace Core
{
    public interface IDevice { }
}


namespace Devices
{
    using Core;

    class DeviceOne : IDevice { }

    class DeviceTwo : IDevice { }

    public class Factory
    {
        public IDevice CreateDeviceOne()
        {
            return new DeviceOne();
        }

        public IDevice CreateDeviceTwo()
        {
            return new DeviceTwo();
        }
    }
}

// do not implement IDevice
namespace ThirdrdPartyDevices
{

    public class ThirdPartyDeviceOne  { }

    public class ThirdPartyDeviceTwo  { }

}

namespace DeviceAdapters
{
    using Core;
    using ThirdPartyDevices;

    class ThirdPartyDeviceAdapterOne : IDevice
    {
        private ThirdPartyDeviceOne _deviceOne;

        // use the third party device to adapt to the interface
    }

    class ThirdPartyDeviceAdapterTwo : IDevice
    {
        private ThirdPartyDeviceTwo _deviceTwo;

        // use the third party device to adapt to the interface
    }

    public class AdapterFactory
    {
        public IDevice CreateThirdPartyDeviceAdapterOne()
        {
            return new ThirdPartyDeviceAdapterOne();
        }

        public IDevice CreateThirdPartyDeviceAdapterTwo()
        {
            return new ThirdPartyDeviceAdapterTwo();
        }
    }
}

namespace Application
{
    using Core;
    using Devices;
    using DeviceAdapters;

    class App
    {
        void RunInHouse()
        {
            var factory = new Factory();
            var devices = new List<IDevice>() { factory.CreateDeviceOne(), factory.CreateDeviceTwo() };
            foreach (var device in devices)
            {
                // call IDevice  methods.
            }
        }

        void RunThirdParty()
        {
            var factory = new AdapterFactory();
            var devices = new List<IDevice>() { factory.CreateThirdPartyDeviceAdapterOne(), factory.CreateThirdPartyDeviceAdapterTwo() };
            foreach (var device in devices)
            {
                // call IDevice  methods.
            }
        }
    }
}

ดังนั้นด้วยการนำไปปฏิบัตินี้สิ่งที่เป็นรูปธรรมจะไปไหน
Snoop

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

ฉันเดาว่าสิ่งเดียวคือแอปพลิเคชันจะเข้าถึงคอนกรีตเฉพาะที่ฉันต้องการได้อย่างไร
Snoop

ทางออกหนึ่งคือใช้รูปแบบนามธรรมจากโรงงานเพื่อสร้าง IDevices
ราคา Jones

1

ข้อสงสัยเดียวในใจของฉันคือตอนนี้ทั้งแอปพลิเคชั่นและคลาสที่เป็นรูปธรรมของอุปกรณ์นั้นขึ้นอยู่กับไลบรารีที่มีส่วนต่อประสาน IDevice แต่นั่นเป็นสิ่งที่ไม่ดี?

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

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

เพียงแค่ใช่

รูปธรรมของแอพเป็นอย่างไร

แอปพลิเคชันสามารถโหลดชุดประกอบคอนกรีต (dll) ที่รันไทม์ ดูที่: /programming/465488/can-i-load-a-net-assembly-at-runtime-and-instantiate-a-type-knowing-only-the-na

ถ้าคุณต้องการแบบไดนามิกขน / โหลดเช่น "คนขับรถ" คุณจำเป็นต้องโหลดลงในการชุมนุมแยกAppDomain


ขอบคุณฉันหวังว่าฉันจะรู้มากขึ้นเกี่ยวกับอินเทอร์เฟซเมื่อฉันเริ่มเขียนโปรแกรมครั้งแรก
Snoop

ขออภัยลืมที่จะเพิ่มในส่วนหนึ่ง (วิธีที่เป็นรูปธรรมจริง ๆ ไปที่แอป) คุณสามารถพูดได้หรือไม่ ดู # 5
Snoop

คุณทำแอพของคุณด้วยวิธีนี้จริง ๆ แล้วโหลด DLLs ณ เวลาทำงานหรือไม่?
Snoop

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