รูปแบบโรงงานใน C #: จะแน่ใจได้อย่างไรว่าอินสแตนซ์ออบเจ็กต์สามารถสร้างได้โดยคลาสโรงงานเท่านั้น


94

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

ลองดูตัวอย่าง:

public MyBusinessObjectClass
{
    public string MyProperty { get; private set; }

    public MyBusinessObjectClass (string myProperty)
    {
        MyProperty = myProperty;
    }
}

public MyBusinessLogicClass
{
    public MyBusinessObjectClass CreateBusinessObject (string myProperty)
    {
        // Perform some check on myProperty

        if (true /* check is okay */)
            return new MyBusinessObjectClass (myProperty);

        return null;
    }
}

ไม่เป็นไรจนกว่าคุณจะจำได้ว่าคุณยังสามารถสร้างอินสแตนซ์ MyBusinessObjectClass ได้โดยตรงโดยไม่ต้องตรวจสอบอินพุต ฉันต้องการยกเว้นความเป็นไปได้ทางเทคนิคนั้นทั้งหมด

แล้วชุมชนคิดอย่างไรเกี่ยวกับเรื่องนี้?


เมธอด MyBoClass สะกดผิดหรือไม่
pradeeptp

2
ฉันสับสนทำไมคุณไม่ต้องการให้วัตถุทางธุรกิจของคุณเป็นวัตถุที่รับผิดชอบในการปกป้องความไม่แน่นอนของตัวเอง จุดสำคัญของรูปแบบโรงงาน (ตามที่ฉันเข้าใจ) คือการ A) จัดการกับการสร้างคลาสที่ซับซ้อนที่มีการอ้างอิงจำนวนมากหรือ B) ให้โรงงานเป็นผู้ตัดสินใจว่าจะส่งคืนรันไทม์ประเภทใดโดยเปิดเผยเฉพาะอินเทอร์เฟซ / นามธรรมเท่านั้น คลาสให้กับลูกค้า การวางกฎเกณฑ์ทางธุรกิจไว้ในวัตถุทางธุรกิจถือเป็นสิ่งจำเป็นอย่างยิ่งของ OOP
sara

@kai True วัตถุทางธุรกิจมีหน้าที่ในการปกป้องความไม่แปรเปลี่ยน แต่ผู้เรียกผู้สร้างต้องตรวจสอบให้แน่ใจว่าอาร์กิวเมนต์นั้นถูกต้องก่อนที่จะส่งต่อไปยังตัวสร้าง ! วัตถุประสงค์ของCreateBusinessObjectวิธีการคือเพื่อตรวจสอบความถูกต้องอาร์กิวเมนต์และเพื่อ (ส่งคืนอ็อบเจ็กต์ที่ถูกต้องใหม่หรือส่งคืนข้อผิดพลาด) ในการเรียกใช้เมธอดเดียวเมื่อผู้เรียกไม่ทราบในทันทีว่าอาร์กิวเมนต์ถูกต้องสำหรับการส่งผ่านไปยังตัวสร้าง
Lightman

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

คำตอบ:


55

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

public BusinessClass
{
    public string MyProperty { get; private set; }

    private BusinessClass()
    {
    }

    private BusinessClass(string myProperty)
    {
        MyProperty = myProperty;
    }

    public static BusinessClass CreateObject(string myProperty)
    {
        // Perform some check on myProperty

        if (/* all ok */)
            return new BusinessClass(myProperty);

        return null;
    }
}

เรียกได้ว่าค่อนข้างตรงไปตรงมา:

BusinessClass objBusiness = BusinessClass.CreateObject(someProperty);

6
อ่าคุณสามารถโยนข้อยกเว้นแทนการคืนค่าว่างได้
Ricardo Nolde

5
ไม่มีประเด็นในการมีตัวสร้างเริ่มต้นส่วนตัวหากคุณได้ประกาศตัวสร้างที่กำหนดเอง นอกจากนี้ในกรณีนี้ไม่มีอะไรที่จะได้รับจากการใช้ "คอนสตรัคเตอร์" แบบคงที่แทนที่จะทำการตรวจสอบความถูกต้องในตัวสร้างจริง (เนื่องจากมันเป็นส่วนหนึ่งของคลาสอยู่แล้ว) มันยังแย่กว่าเดิมเนื่องจากตอนนี้คุณสามารถรับค่าผลตอบแทนที่เป็นโมฆะเปิดขึ้นสำหรับ NREs และ "นี่มันหมายความว่าอะไร" คำถาม
sara

มีวิธีใดดีที่จะต้องใช้สถาปัตยกรรมนี้ผ่านอินเทอร์เฟซ / คลาสฐานนามธรรม เช่นอินเทอร์เฟซ / คลาสฐานนามธรรมที่กำหนดว่าผู้ใช้งานไม่ควรเปิดเผย ctor
Arash Motamedi

1
@Arash No. อินเทอร์เฟซไม่สามารถกำหนดตัวสร้างได้และคลาสนามธรรมที่กำหนดตัวสร้างที่ได้รับการป้องกันหรือตัวสร้างภายในไม่สามารถป้องกันไม่ให้คลาสที่สืบทอดมาเปิดเผยผ่านตัวสร้างสาธารณะของตัวเอง
Ricardo Nolde

66

คุณสามารถทำให้ตัวสร้างเป็นแบบส่วนตัวและโรงงานเป็นประเภทที่ซ้อนกัน:

public class BusinessObject
{
    private BusinessObject(string property)
    {
    }

    public class Factory
    {
        public static BusinessObject CreateBusinessObject(string property)
        {
            return new BusinessObject(property);
        }
    }
}

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


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

@ so-tester: คุณยังสามารถตรวจสอบในโรงงานแทนประเภท BusinessObject ได้
Jon Skeet

3
@JonSkeet ฉันรู้ว่าคำถามนี้เก่าจริงๆ แต่ฉันอยากรู้ว่าข้อดีของการวางCreateBusinessObjectวิธีการไว้ในFactoryคลาสที่ซ้อนกันแทนที่จะให้วิธีการคงที่เป็นวิธีการโดยตรงของBusinessObjectคลาส ... คุณจำได้ไหม แรงจูงใจในการทำเช่นนั้น?
Kiley Naro

3
@KileyNaro: นั่นคือสิ่งที่คำถามกำลังถาม :) มันไม่จำเป็นต้องให้ข้อดีมากมาย แต่ตอบคำถาม ... อาจมีบางครั้งที่สิ่งนี้มีประโยชน์ - รูปแบบผู้สร้างจะเกิดขึ้นในใจ (ในกรณีนี้ตัวสร้างจะเป็นคลาสที่ซ้อนกันและจะมีวิธีการอินสแตนซ์ที่เรียกว่าBuild)
Jon Skeet

53

หรือถ้าคุณต้องการที่จะจินตนาการจริงๆให้กลับการควบคุม: ให้ชั้นเรียนส่งคืนโรงงานและใช้โรงงานกับตัวแทนที่สามารถสร้างคลาสได้

public class BusinessObject
{
  public static BusinessObjectFactory GetFactory()
  {
    return new BusinessObjectFactory (p => new BusinessObject (p));
  }

  private BusinessObject(string property)
  {
  }
}

public class BusinessObjectFactory
{
  private Func<string, BusinessObject> _ctorCaller;

  public BusinessObjectFactory (Func<string, BusinessObject> ctorCaller)
  {
    _ctorCaller = ctorCaller;
  }

  public BusinessObject CreateBusinessObject(string myProperty)
  {
    if (...)
      return _ctorCaller (myProperty);
    else
      return null;
  }
}

:)


รหัสที่ดีมาก ตรรกะการสร้างอ็อบเจ็กต์อยู่ในคลาสแยกต่างหากและอ็อบเจ็กต์สามารถสร้างได้โดยใช้โรงงานเท่านั้น
Nikolai Samteladze

เย็น. มีวิธีใดบ้างที่คุณสามารถ จำกัด การสร้าง BusinessObjectFactory ไว้ที่ BusinessObject.GetFactory แบบคงที่ได้หรือไม่
Felix Keil

1
ข้อดีของการผกผันนี้คืออะไร?
yatskovsky

1
@yatskovsky เป็นเพียงวิธีการตรวจสอบให้แน่ใจว่าวัตถุนั้นสามารถสร้างอินสแตนซ์ได้เฉพาะในรูปแบบที่มีการควบคุมในขณะที่ยังสามารถแยกตรรกะการสร้างออกเป็นคลาสเฉพาะได้
Fabian Schmied

15

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


7

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

public class BusinessObject
{
  public static Create (string myProperty)
  {
    if (...)
      return new BusinessObject (myProperty);
    else
      return null;
  }
}

แต่คำถามที่แท้จริงคือ - ทำไมคุณถึงมีข้อกำหนดนี้? เป็นที่ยอมรับในการย้ายโรงงานหรือวิธีการโรงงานไปในชั้นเรียนหรือไม่?


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

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

7

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

อินเทอร์เฟซสำหรับชั้นเรียนของคุณ:

public interface FactoryObject
{
    FactoryObject Instantiate();
}

ชั้นเรียนของคุณ:

public class YourClass : FactoryObject
{
    static YourClass()
    {
        Factory.RegisterType(new YourClass());
    }

    private YourClass() {}

    FactoryObject FactoryObject.Instantiate()
    {
        return new YourClass();
    }
}

และในที่สุดโรงงาน:

public static class Factory
{
    private static List<FactoryObject> knownObjects = new List<FactoryObject>();

    public static void RegisterType(FactoryObject obj)
    {
        knownObjects.Add(obj);
    }

    public static T Instantiate<T>() where T : FactoryObject
    {
        var knownObject = knownObjects.Where(x => x.GetType() == typeof(T));
        return (T)knownObject.Instantiate();
    }
}

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


4

อีกทางเลือกหนึ่ง (น้ำหนักเบา) คือการสร้างวิธีโรงงานแบบคงที่ในคลาส BusinessObject และทำให้ตัวสร้างเป็นแบบส่วนตัว

public class BusinessObject
{
    public static BusinessObject NewBusinessObject(string property)
    {
        return new BusinessObject();
    }

    private BusinessObject()
    {
    }
}

2

ดังนั้นดูเหมือนว่าสิ่งที่ฉันต้องการไม่สามารถทำได้ด้วยวิธีที่ "บริสุทธิ์" มันมักจะ "เรียกกลับ" ไปที่คลาสตรรกะ

บางทีฉันอาจทำได้ด้วยวิธีง่ายๆเพียงแค่สร้างเมธอด contructor ในคลาสอ็อบเจ็กต์ก่อนเรียกคลาสลอจิกเพื่อตรวจสอบอินพุต?

public MyBusinessObjectClass
{
    public string MyProperty { get; private set; }

    private MyBusinessObjectClass (string myProperty)
    {
        MyProperty  = myProperty;
    }

    pubilc static MyBusinessObjectClass CreateInstance (string myProperty)
    {
        if (MyBusinessLogicClass.ValidateBusinessObject (myProperty)) return new MyBusinessObjectClass (myProperty);

        return null;
    }
}

public MyBusinessLogicClass
{
    public static bool ValidateBusinessObject (string myProperty)
    {
        // Perform some check on myProperty

        return CheckResult;
    }
}

ด้วยวิธีนี้วัตถุทางธุรกิจไม่สามารถสร้างได้โดยตรงและวิธีการตรวจสอบสาธารณะในตรรกะทางธุรกิจจะไม่เป็นอันตรายเช่นกัน


2

ในกรณีของการแยกที่ดีระหว่างอินเทอร์เฟซและการใช้งาน
Protected-constructor-public-initializer ช่วยให้สามารถแก้ปัญหาได้อย่างเรียบร้อย

ให้วัตถุทางธุรกิจ:

public interface IBusinessObject { }

class BusinessObject : IBusinessObject
{
    public static IBusinessObject New() 
    {
        return new BusinessObject();
    }

    protected BusinessObject() 
    { ... }
}

และโรงงานธุรกิจ:

public interface IBusinessFactory { }

class BusinessFactory : IBusinessFactory
{
    public static IBusinessFactory New() 
    {
        return new BusinessFactory();
    }

    protected BusinessFactory() 
    { ... }
}

การเปลี่ยนแปลงBusinessObject.New()initializer ต่อไปนี้ช่วยแก้ปัญหา:

class BusinessObject : IBusinessObject
{
    public static IBusinessObject New(BusinessFactory factory) 
    { ... }

    ...
}

ที่นี่จำเป็นต้องมีการอ้างอิงถึงโรงงานธุรกิจคอนกรีตเพื่อเรียกตัวBusinessObject.New()เริ่มต้น แต่ผู้เดียวที่มีข้อมูลอ้างอิงที่จำเป็นคือโรงงานของธุรกิจเอง

เรามีสิ่งที่เราต้องการ: เพียงคนเดียวที่สามารถสร้างเป็นBusinessObjectBusinessFactory


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

@ Flater เห็นด้วย ฉันจะเปลี่ยนชื่อพารามิเตอร์factoryเป็นasFriend. ในฐานรหัสของฉันมันจะแสดงเป็นpublic static IBusinessObject New(BusinessFactory asFriend)
Reuven Bass

ฉันไม่เข้าใจเลย มันทำงานอย่างไร? โรงงานไม่สามารถสร้างอินสแตนซ์ใหม่ของ IBusinessObject และไม่มีเจตนา (วิธีการ) ที่จะทำเช่นนั้น ... ?
lapsus

2
    public class HandlerFactory: Handler
    {
        public IHandler GetHandler()
        {
            return base.CreateMe();
        }
    }

    public interface IHandler
    {
        void DoWork();
    }

    public class Handler : IHandler
    {
        public void DoWork()
        {
            Console.WriteLine("hander doing work");
        }

        protected IHandler CreateMe()
        {
            return new Handler();
        }

        protected Handler(){}
    }

    public static void Main(string[] args)
    {
        // Handler handler = new Handler();         - this will error out!
        var factory = new HandlerFactory();
        var handler = factory.GetHandler();

        handler.DoWork();           // this works!
    }

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

1
ในตัวอย่างของคุณสิ่งต่อไปนี้จะเป็นไปไม่ได้ (ซึ่งไม่สมเหตุสมผล)?: factory.DoWork ();
Whyser

1

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

public class Person
{
  internal Person()
  {
  }
}

public class PersonFactory
{
  public Person Create()
  {
    return new Person();
  }  
}

อย่างไรก็ตามฉันต้องตั้งคำถามกับแนวทางของคุณ :-)

ฉันคิดว่าถ้าคุณต้องการให้คลาส Person ของคุณถูกต้องเมื่อสร้างคุณต้องใส่รหัสในตัวสร้าง

public class Person
{
  public Person(string firstName, string lastName)
  {
    FirstName = firstName;
    LastName = lastName;
    Validate();
  }
}

1

การแก้ปัญหานี้จะตามออกmunificentsคิดของการใช้สัญลักษณ์ในการสร้าง ทำในคำตอบนี้ให้แน่ใจว่าวัตถุที่สร้างโดยโรงงานเท่านั้น (C #)

  public class BusinessObject
    {
        public BusinessObject(object instantiator)
        {
            if (instantiator.GetType() != typeof(Factory))
                throw new ArgumentException("Instantiator class must be Factory");
        }

    }

    public class Factory
    {
        public BusinessObject CreateBusinessObject()
        {
            return new BusinessObject(this);
        }
    }

1

มีการกล่าวถึงหลายวิธีที่มีการแลกเปลี่ยนที่แตกต่างกัน

  • การซ้อนคลาสโรงงานในคลาสที่สร้างแบบส่วนตัวจะอนุญาตให้โรงงานสร้างคลาสได้ 1 คลาสเท่านั้น ณ จุดนั้นคุณควรใช้Createวิธีการและ ctor ส่วนตัวดีกว่า
  • การใช้มรดกและ ctor ที่ได้รับการป้องกันมีปัญหาเดียวกัน

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

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

== ChemicalFactory.cs ==
partial class ChemicalFactory {
    private  ChemicalFactory() {}

    public interface IChemical {
        int AtomicNumber { get; }
    }

    public static IChemical CreateOxygen() {
        return new Oxygen();
    }
}


== Oxygen.cs ==
partial class ChemicalFactory {
    private class Oxygen : IChemical {
        public Oxygen() {
            AtomicNumber = 8;
        }
        public int AtomicNumber { get; }
    }
}



== Program.cs ==
class Program {
    static void Main(string[] args) {
        var ox = ChemicalFactory.CreateOxygen();
        Console.WriteLine(ox.AtomicNumber);
    }
}

0

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


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

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

@ จิม: ฉันเห็นด้วยกับประเด็นของคุณเป็นการส่วนตัว แต่โดยมืออาชีพสิ่งนี้จะทำอย่างต่อเนื่อง โครงการปัจจุบันของฉันคือบริการเว็บที่ใช้สตริง HTML ล้างข้อมูลตามกฎที่กำหนดไว้ล่วงหน้าจากนั้นส่งคืนสตริงที่ล้างข้อมูล ฉันต้องใช้รหัสของตัวเอง 7 ชั้น "เพราะโครงการทั้งหมดของเราต้องสร้างจากเทมเพลตโครงการเดียวกัน" เป็นเหตุผลว่าคนส่วนใหญ่ที่ถามคำถามเหล่านี้ใน SO ไม่ได้รับอิสระในการเปลี่ยนแปลงขั้นตอนทั้งหมดของโค้ด
Flater

0

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

ทางเลือกหนึ่งคือการมีตัวสร้างแบบคงที่ซึ่งจะลงทะเบียนโรงงานที่ไหนสักแห่งที่มีบางอย่างเช่นคอนเทนเนอร์ IOC


0

นี่คืออีกทางเลือกหนึ่งของ "เพียงเพราะคุณไม่ได้หมายความว่าคุณควร" ...

เป็นไปตามข้อกำหนดในการรักษาตัวสร้างวัตถุทางธุรกิจให้เป็นส่วนตัวและวางตรรกะของโรงงานไว้ในคลาสอื่น หลังจากนั้นก็จะร่างเล็กน้อย

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

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

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

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

using System;

public class MyBusinessObjectClass
{
    public string MyProperty { get; private set; }

    private MyBusinessObjectClass(string myProperty)
    {
        MyProperty = myProperty;
    }

    // Need accesible default constructor, or else MyBusinessObjectFactory declaration will generate:
    // error CS0122: 'MyBusinessObjectClass.MyBusinessObjectClass(string)' is inaccessible due to its protection level
    protected MyBusinessObjectClass()
    {
    }

    protected static MyBusinessObjectClass Construct(string myProperty)
    {
        return new MyBusinessObjectClass(myProperty);
    }
}

public abstract class MyBusinessObjectFactory : MyBusinessObjectClass
{
    public static MyBusinessObjectClass CreateBusinessObject(string myProperty)
    {
        // Perform some check on myProperty

        if (true /* check is okay */)
            return Construct(myProperty);

        return null;
    }

    private MyBusinessObjectFactory()
    {
    }
}

0

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

public static class Runnable
{
    public static void Run()
    {
        MyClass myClass = MyClassPrivilegeKey.MyClassFactory.GetInstance();
    }
}

public abstract class MyClass
{
    public MyClass(MyClassPrivilegeKey key) { }
}

public class MyClassA : MyClass
{
    public MyClassA(MyClassPrivilegeKey key) : base(key) { }
}

public class MyClassB : MyClass
{
    public MyClassB(MyClassPrivilegeKey key) : base(key) { }
}


public class MyClassPrivilegeKey
{
    private MyClassPrivilegeKey()
    {
    }

    public static class MyClassFactory
    {
        private static MyClassPrivilegeKey key = new MyClassPrivilegeKey();

        public static MyClass GetInstance()
        {
            if (/* some things == */true)
            {
                return new MyClassA(key);
            }
            else
            {
                return new MyClassB(key);
            }
        }
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.