Многоуровневое наследование с плавным интерфейсом в C # - PullRequest
1 голос
/ 30 августа 2009

Приведенный ниже пример консольного приложения:

ВОПРОС # 1 : Почему .Name () возвращает тип OranizationBuilder, а .Write () вызывает CorporationBuilder?

ВОПРОС # 2 : Как получить .Name () для возврата typeof CorporationBuilder?

namespace MyCompany
{
    using System;

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(Environment.NewLine);

            Factory.Organization()
                    .ID(33)
                    .Name("Oranization A")
                    .Write();

            Console.WriteLine("\n----------------------------\n");

            Factory.Corporation()
                    .Date(DateTime.Today)     // Pass
                    .ID(44)
                    .Name("Company B")
                    // .Date(DateTime.Today)  // Fail
                    .Write();

            // QUESTION #1: Why does .Name() return typeof OranizationBuilder, 
            //              but .Write() calls CorporationBuilder?

            // QUESTION #2: How to get .Name() to return typeof CorporationBuilder?


            Console.ReadLine();
        }
    }

    /* Business Classes */

    public abstract class Contact
    {
        public int ID { get; set; }
    }

    public class Organization : Contact
    {
        public string Name { get; set; }
    }

    public class Corporation : Organization
    {
        public DateTime Date { get; set; }
    }


    /* Builder */

    public abstract class ContactBuilder<TContact, TBuilder>
        where TContact : Contact
        where TBuilder : ContactBuilder<TContact, TBuilder>
    {
        public ContactBuilder(TContact contact)
        {
            this.contact = contact;
        }

        private TContact contact;

        public TContact Contact
        {
            get
            {
                return this.contact;
            }
        }

        public virtual TBuilder ID(int id)
        {
            this.Contact.ID = id;
            return this as TBuilder;
        }

        public virtual void Write()
        {
            Console.WriteLine("ID   : {0}", this.Contact.ID);
        }
    }

    public class OrganizationBuilder : ContactBuilder<Organization, OrganizationBuilder>
    {
        public OrganizationBuilder(Organization contact) : base(contact) { }

        public virtual OrganizationBuilder Name(string name)
        {
            (this.Contact as Organization).Name = name;
            return this;
        }

        public override void Write()
        {
            base.Write();
            Console.WriteLine("Name : {0}", this.Contact.Name);
        }
    }

    public class CorporationBuilder : OrganizationBuilder
    {
        public CorporationBuilder(Corporation contact) : base(contact) { }

        public virtual CorporationBuilder Date(DateTime date)
        {
            // Cast is required, but need this.Contact to be typeof 'C'
            (this.Contact as Corporation).Date = date;
            return this;
        }

        public override void Write()
        {
            base.Write();
            Console.WriteLine("Date : {0}", (this.Contact as Corporation).Date.ToShortDateString());
        }
    }

    /* Factory */

    public class Factory
    {
        public static OrganizationBuilder Organization()
        {
            return new OrganizationBuilder(new Organization());
        }

        public static CorporationBuilder Corporation()
        {
            return new CorporationBuilder(new Corporation());
        }
    }
}

EDIT / UPDATE

Вот моя первая попытка найти решение (см. Ниже), хотя я застрял внутри Factory и не уверен, как настроить типы методов .Organization () и .Corporation ().

namespace MyCompany
{
    using System;

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(Environment.NewLine);

            Factory.Organization()
                    .ID(33)
                    .Name("Oranization A")
                    .Write();

            Console.WriteLine("\n----------------------------\n");

            Factory.Corporation()
                    .ID(44)
                    .Name("Company B")
                    .Date(DateTime.Today)
                    .Write();

            Console.ReadLine();
        }
    }


    /* Business Classes */

    public abstract class Contact
    {
        public int ID { get; set; }
    }

    public class Organization : Contact
    {
        public string Name { get; set; }
    }

    public class Corporation : Organization
    {
        public DateTime Date { get; set; }
    }


    /* Builder */

    public abstract class ContactBuilder<TContact, TBuilder>
        where TContact : Contact
        where TBuilder : ContactBuilder<TContact, TBuilder>
    {
        public ContactBuilder(TContact contact)
        {
            this.contact = contact;
        }

        private TContact contact;

        public TContact Contact
        {
            get
            {
                return this.contact;
            }
        }

        public virtual TBuilder ID(int id)
        {
            this.Contact.ID = id;
            return this as TBuilder;
        }

        public virtual void Write()
        {
            Console.WriteLine("ID   : {0}", this.Contact.ID);
        }
    }

    public class OrganizationBuilder<TOrganization, TBuilder> : ContactBuilder<TOrganization, TBuilder> where TOrganization : Organization where TBuilder : OrganizationBuilder<TOrganization, TBuilder>
    {
        public OrganizationBuilder(TOrganization contact) : base(contact) { }

        public virtual TBuilder Name(string name)
        {
            this.Contact.Name = name;
            return this as TBuilder;
        }

        public override void Write()
        {
            base.Write();
            Console.WriteLine("Name : {0}", this.Contact.Name);
        }
    }

    public class CorporationBuilder<TCorporation, TBuilder> : OrganizationBuilder<TCorporation, TBuilder> where TCorporation : Corporation where TBuilder : CorporationBuilder<TCorporation, TBuilder>
    {
        public CorporationBuilder(TCorporation contact) : base(contact) { }

        public virtual TBuilder Date(DateTime date)
        {
            this.Contact.Date = date;
            return this as TBuilder;
        }

        public override void Write()
        {
            base.Write();
            Console.WriteLine("Date : {0}", this.Contact.Date.ToShortDateString());
        }
    }


    /* Factory */

    public class Factory
    {
        public static OrganizationBuilder<Organization, OrganizationBuilder> Organization()
        {
            return new OrganizationBuilder<Organization, OrganizationBuilder>(new Organization());
        }

        public static CorporationBuilder<Corporation, CorporationBuilder> Corporation()
        {
            return new CorporationBuilder<Corporation, CorporationBuilder>(new Corporation());
        }
    }
}

Вот конкретная проблемная область:

/* Factory */

public class Factory
{
    public static OrganizationBuilder<Organization, OrganizationBuilder> Organization()
    {
        return new OrganizationBuilder<Organization, OrganizationBuilder>(new Organization());
    }

    public static CorporationBuilder<Corporation, CorporationBuilder> Corporation()
    {
        return new CorporationBuilder<Corporation, CorporationBuilder>(new Corporation());
    }
}

Как настроить Организационный Билдер и Корпоративный Билдер?

Ответы [ 2 ]

4 голосов
/ 30 августа 2009

Когда Name возвращает ссылку, она возвращает this - поэтому, когда экземпляр на самом деле экземпляр CorporationBuilder, эта ссылка возвращается как обычно. То, что метод объявлен как возвращающий OrganizationBuilder, не означает, что только возвращает ссылку OrganizationBuilder. Он возвращает ссылку на экземпляр OrganizationBuilder instance или производный класс (или, конечно, null).

Когда затем вызывается метод Write, это виртуальный метод, поэтому проверяется тип времени выполнения объекта, чтобы найти используемую реализацию. Тип времени выполнения по-прежнему CorporationBuilder, поэтому используется переопределение, указанное в этом типе.

Что касается того, как заставить Name() возвращать соответствующий тип - для этого, в основном, потребуется больше обобщений. Это может быть сделано, но это боль - я сделал нечто подобное в Protocol Buffers, но это не приятно. Вы также сделаете OrganizationBuilder родовым в TContact и TBuilder, а также Name вернете TBuilder с помощью приведения от this до TBuilder. Тогда CorporationBuilder будет либо общим, либо просто наследоваться от OrganizationBuilder<Corporation, CorporationBuilder>.

РЕДАКТИРОВАТЬ: Да, я вижу проблему (о которой я забыл раньше). Вы также можете захотеть иметь конкретный неуниверсальный класс с именем CorporationBuilder, чтобы избежать рекурсивных обобщений:

public class OrganizationBuilder :
    OrganizationBuilder<Organization, OrganizationBuilder>

Вы также можете переименовать OrganizationBuilder в OrganizationBuilderBase, чтобы избежать путаницы:)

(Вам не нужно, чтобы CorporationBuilder был сам по себе универсальным, если он находится в нижней части иерархии.)

Однако, это становится чрезвычайно сложным. Возможно, вы захотите хотя бы рассмотреть избегая наследования здесь. Удалите дженерики и заставьте OrganizationBuilder иметь a CorporationBuilder вместо того, чтобы извлекать из него.

По сути, этот шаблон всегда усложняется через некоторое время - вам нужно, чтобы каждый уровень был универсальным, за исключением конечных узлов, которые всегда должны быть неуниверсальными, чтобы избежать проблемы рекурсии, которую вы уже видели. Это боль.

0 голосов
/ 30 августа 2009

Функция .Name () в OrganizationBuilder имеет подпись для возврата типа OrganizationBuilder - независимо от того, из какого производного объекта она вызывается. Вот почему вы видите, что он возвращает OrganizationBuilder. Если вы хотите переопределить функцию Name () в конструкторе контрактов и установить имя в другое значение, вы заметите, что функция Name () действует на ваш объект времени выполнения.

Теперь, если вы хотите знать, как заставить Name () возвращать нужный вам конструктор, вы должны следовать той же методике, что и для метода ID ().

EDIT / UPDATE: Ну, теперь я не понимаю фактическую ошибку, с которой вы сталкиваетесь - с новыми обновлениями. Можете ли вы поделиться точной ошибкой, с которой вы столкнулись?

На заметку: я чувствую, что этот дизайн полностью запутан. Я не дал бы это своим потребителям только для того, чтобы поддержать хороший шаблон метода конструктора, возвращающего соответствующий объект компоновщика. Я бы придерживался гораздо более простого подхода.

...