Дизайн для кроссплатформенных классов в C # - PullRequest
3 голосов
/ 22 ноября 2010

Резюме: Я хочу знать лучший дизайн для создания кроссплатформенных (например, настольных, веб-и Silverlight) классов в C #, без дублирования кода, с плюсами и минусами каждого дизайн.

Я часто пишу новые, полезные классы для одного домена приложения; нет причин, по которым они не будут работать в разных доменах. Как я могу структурировать свой код, чтобы он идеально кроссплатформенный?

Например, допустим, я хотел создать общий класс "MyTimer" с интервалом и событием на тике. В настольном компьютере это будет использовать встроенный таймер .NET. В Silverlight я бы использовал DispatchTimer.

Дизайн # 1 может быть «создать класс и использовать директивы препроцессора для условной компиляции», например. "#IF SILVERILGHT ...". Однако это приводит к тому, что код становится менее понятным, читаемым и поддерживаемым.

Дизайн # 2 может быть «создавать подклассы, называемые DesktopTimer и SilverlightTimer, и использовать их из MyTimer». Как это будет работать?

Хотя это тривиальный случай, у меня могут быть более сложные классы, которые, например, используют классы, специфичные для платформы (IsolatedStorage, DispatchTimer и т. Д.), Но не заменяют их напрямую.

Какие еще конструкции / парадигмы я могу использовать?

Ответы [ 7 ]

4 голосов
/ 22 ноября 2010

Я бы предложил написать Interface s, которые вы просто внедрили бы для своей платформы для конкретного кода. Затем интерфейсы гарантируют, что ваш код будет соблюдать контракты, заданные вашим интерфейсом, в противном случае произойдет разрыв кода (если один участник не реализован).

Кроме того, в этой библиотеке, где находятся ваши конкретные классы таймеров, чтобы придерживаться вашего примера, я бы создал класс для каждой платформы, используя, таким образом, DispatchTimer для Silverlight и встроенный таймер .NET для рабочего стола. версия.

В конце концов, вы в конечном итоге будете использовать только один интерфейс, который только его разработчики знают, как обращаться с контрактом специально для вашей базовой платформы.

РЕДАКТИРОВАТЬ # 1

Условный дизайн - не вариант для хорошего дизайна. Вот инструмент, который поможет вам разобраться с Dependancy Injection, который называется Unity Application Block, и используется для решения такого сценария, как ваш.

Вы используете только универсальную конфигурацию XML, чтобы «сказать», что нужно создать, когда нужен тот или иной интерфейс. Затем UnityContainer проконсультируется с созданной вами конфигурацией и создаст подходящий вам класс. Это обеспечивает хороший подход к дизайну и архитектуре.

РЕДАКТИРОВАТЬ # 2

Я не очень знаком с Dependency Injection и совсем не знаком с Unity Application Block. Можете ли вы указать на некоторые ресурсы или объяснить их немного дальше?

  1. Microsoft Enterprise Library 5.0 - апрель 2010 ;
  2. Microsoft Unity 2.0 - апрель 2010 ;
  3. Документация по Microsoft Unity 2.0 для Visual Studio 2008 ;
  4. Существуют ли хорошие уроки / пошаговые руководства для Unity, в которых не используются файлы конфигурации? (ТАК вопрос по теме, который должен дать ценные советы, чтобы начать с Unity);
  5. Указание типов в файле конфигурации ;
  6. Пошаговое руководство: быстрый старт Unity StopLight ;
  7. Пошаговое руководство. Краткое описание расширения для Unity Event Broker .

Я думаю, что эти ресурсы помогут вам в вашем обучении. Если вам нужна дополнительная помощь, пожалуйста, дайте мне знать! =)

РЕДАКТИРОВАТЬ # 3

Но в любом случае быстрый запуск StopLight [...], по-видимому, подразумевает, что отображение зависимости интерфейса от конкретного класса выполняется в коде (что мне не подойдет).

На самом деле, вы можете выполнять как кодирование, так и отображение зависимостей XML, выбор за вами! =)

Вот несколько примеров, из которых вам, возможно, стоит вдохновить быстрый старт StopLight на использование конфигурации XML вместо кодированного отображения.

  1. Проверка конфигурации XML Unity ;
  2. Использование конфигурации времени проектирования ;
  3. Исходная схема для блока приложений Unity .

Если это не поможет вам пройти, дайте мне знать. Затем я приведу простой пример использования сопоставления XML. =) * +1088 *

1 голос
/ 22 ноября 2010

Модель Model-View-Presenter - это действительно хороший подход, если вы хотите отделить всю логику вашего пользовательского интерфейса от реальной используемой вами структуры GUI.Прочтите статью Майкла Фезера «Диалоговое окно« Смирение »», чтобы получить отличное объяснение того, как это работает:

http://www.objectmentor.com/resources/articles/TheHumbleDialogBox.pdf

Оригинальная статья была написана для C ++, если вы хотите пример на C #,посмотрите здесь:

http://codebetter.com/blogs/jeremy.miller/articles/129546.aspx

Плюсы:

  • вы сделаете свою логику GUI повторно используемой
  • ваша логика GUI станет применимой длямодульное тестирование

Минусы:

  • , если вашей программе не требуется более одного каркаса графического интерфейса, этот подход создает больше строк кода, и вам приходится иметь дело сс большей сложностью, так как вы должны во всем кодировании решить, какие части вашего кода принадлежат представлению, а какие - ведущему
1 голос
/ 22 ноября 2010

1) Интерфейсы со специфичным для платформы классом в их собственных сборках: ITimer в общей сборке и, например, «WebAssembly», содержащая WebTimer.Затем «WebAssembly.dll» или «DesktopAssembly.dll» загружаются по требованию.Это превращает его в проблему развертывания / настройки, и все компилируется.Внедрение Dependency Injection или MEF здесь очень помогают.

2) Интерфейсы (опять же), но с условной компиляцией.Это делает его не столько проблемой развертывания, сколько проблемой компиляции.WebTimer будет иметь #ifdef WEB_PLATFORM вокруг, и так далее.

Лично я бы склонялся к # 1 - но в сложном приложении вам, скорее всего, придется использовать оба из-за небольших изменений в доступных частях .net framework между silverlight и всем остальнымостальное.Возможно, вам даже понадобится другое поведение в основных частях вашего приложения только из-за проблем с производительностью.

1 голос
/ 22 ноября 2010

Иди со всем, что ты знаешь.Я бы предложил создать доменную модель, независимую от платформы (Windows, Mono / destkop, web).Используйте абстрактные классы для моделирования вещей, зависящих от платформы (например, Timer).Используйте шаблоны Dependency Injection и / или Factory для использования определенных реализаций.

EDIT: в какой-то момент вы должны указать, какие конкретные классы использовать, но использование вышеупомянутых шаблонов может привести весь этот код в одно место без использования условныхкомпиляция.

РЕДАКТИРОВАТЬ: пример DI / Factory.Конечно, вы можете использовать существующие фреймворки, что даст вам больше силы и выразительности.Для простого примера это выглядит как избыточное убийство, но чем сложнее код, тем больше выигрыш от использования шаблонов.

// Common.dll

public interface IPlatformInfo
{
    string PlatformName { get; }
}

public interface PlatformFactory
{
    IPlatformInfo CreatePlatformInfo();
    // other...
}

public class WelcomeMessage
{
    private IPlatformInfo platformInfo;

    public WelcomeMessage(IPlatformInfo platformInfo)
    {
        this.platformInfo = platformInfo;
    }

    public string GetMessage()
    {
        return "Welcome at " + platformInfo.PlatformName + "!";
    }
}

// WindowsApp.exe

public class WindowsPlatformInfo : IPlatformInfo
{
    public string PlatformName
    {
        get { return "Windows"; }
    }
}

public class WindowsPlatformFactory : PlatformFactory
{
    public IPlatformInfo CreatePlatformInfo()
    {
        return new WindowsPlatformInfo();
    }
}

public class WindowsProgram
{
    public static void Main(string[] args)
    {
        var factory = new WindowsPlatformFactory();
        var message = new WelcomeMessage(factory.CreatePlatformInfo());
        Console.WriteLine(message.GetMessage());
    }
}

// MonoApp.exe

public class MonoPlatformInfo : IPlatformInfo
{
    public string PlatformName
    {
        get { return "Mono"; }
    }
}

public class MonoPlatformFactory : PlatformFactory
{
    public IPlatformInfo CreatePlatformInfo()
    {
        return new MonoPlatformInfo();
    }
}

public class MonoProgram
{
    public static void Main(string[] args)
    {
        var factory = new MonoPlatformFactory();
        var message = new WelcomeMessage(factory.CreatePlatformInfo());
        Console.WriteLine(message.GetMessage());
    }
}
1 голос
/ 22 ноября 2010

Я думаю, что интерфейсы - хороший выбор здесь (определение того, что таймер будет делать без его фактической реализации)

public interface ITimer
{
    void CreateTimer(int _interval, TimerDelegate _delegate);
    void StopTimer();
    // etc...
} // eo interface ITimer

Отсюда вы получаете конкретные таймеры:

public class DesktopTimer : ITimer
{
} // eo DesktopTimer

public class SilverlightTimer : ITimer
{
} // eo class SilverlightTimer

public class WebTimer : Timer
{
} // eo class WebTimer

Затем начинается самое интересное. Как нам создать правильный таймер? Здесь вы можете реализовать какую-то платформу-фабрику, которая возвращает правильный таймер в зависимости от того, на какой платформе он работает. Вот быстрая и грязная идея (я хотел бы сделать ее более динамичной, чем эта, и, возможно, реализовать одну фабрику для нескольких видов классов, но это пример)

public enum Platform
{
     Desktop,
     Web,
     Silverlight
} // eo enum Platform

public class TimerFactory
{
    private class ObjectInfo
    {
        private string m_Assembly;
        private string m_Type;

        // ctor
        public ObjectInfo(string _assembly, string _type)
        {
            m_Assembly = _assembly;
            m_Type = _type;
        } // eo ctor

        public ITimer Create() {return(AppDomain.CurrentDomain.CreateInstanceAndUnwrap(m_Assembly, m_Type));}
    } // eo class ObjectInfo

    Dictionary<Platform, ObjectInfo> m_Types = new Dictionary<PlatForm, ObjectInfo>();

    public TimerFactory()
    {
        m_Types[Platform.Desktop] = new ObjectInfo("Desktop", "MyNamespace.DesktopTimer");
        m_Types[Platform.Silverlight] = new ObjectInfo("Silverlight", "MyNameSpace.SilverlightTimer");
        // ...
    } // eo ctor

    public ITimer Create()
    {
        // based on platform, create appropriate ObjectInfo
    } // eo Create
} // eo class TimerFactory

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

0 голосов
/ 04 декабря 2010

Почему бы не иметь раздел конфигурации, который расскажет вашей библиотеке о платформе хост-приложения.Для этого вам нужно только один раз установить в вашем приложении файл конфигурации хоста (web.config или app.config), а остальные вы можете использовать, используя метод Factory, как это предложено Moo-Juice.Вы можете использовать детали платформы для всей функциональности библиотеки.

0 голосов
/ 22 ноября 2010

Как и другие, предложили, интерфейсы путь сюда. Я бы немного изменил интерфейс от предложения Moo-Juice sugestion ...

` // Почему этот блок не отформатирован как код ???

открытый интерфейс ITimer {
void StopTimer (); // и т.д ...

void StartTimer (); // и т.д ...

TimeSpan Duration {get;} // eo интерфейс ITimer

} `

Теперь вам нужно будет ввести ITimer в ваш класс, который его использует. Самый простой способ сделать это называется внедрением зависимости. Наиболее распространенный подход для достижения этого называется инжектором конструктора.

Таким образом, при создании класса, которому требуется таймер, вы передаете таймер в класс при его создании.

В основном вы делаете:

var foo = new Foo(new WebTimer());

Поскольку это будет довольно быстро усложняться, вы можете использовать несколько помощников. Эта модель называется инверсией контроля. Есть некоторые рамки, которые помогут вам, например, ниндзя или замок Виндзор.

Оба являются контейнерами с инверсией управления (IOC). (Это секретный соус)

По сути, вы «регистрируете» свой таймер в МОК, а также регистрируете свой «Foo». Когда вам нужен "Foo", вы просите ваш контейнер IOC создать его. Контейнер смотрит на конструктор, находит, что ему нужен ITimer. Затем он создаст для вас ITimer, передаст его в конструктор и, наконец, передаст вам полный класс.

Внутри вашего класса вам не нужно иметь никаких знаний об ITimer или о том, как его создавать, поскольку все это было перемещено наружу.

Для разных Приложений теперь вам нужно только зарегистрировать правильные компоненты, и все готово ...

P.s .: Будьте осторожны и не путайте IOC-контейнер с сервисным локатором ...

Ссылки:

http://ninject.org/download

http://www.castleproject.org/container/index.html

http://www.pnpguidance.net/Category/Unity.aspx

...