Классический дизайн парадокс - PullRequest
0 голосов
/ 28 ноября 2018

Мне нужен класс, который определяет, какой тип сетевого соединения будет выполняться.

Сетевое соединение может быть либо Domain присоединением, либо Workgroup объединением.

В случае Workgroup объединения мне нужно знать только Name рабочей группы для присоединения.

В случае Domain объединения мне нужно знать Nameдомен для присоединения, а также Username и Password для использования (давайте проигнорируем все опасения по поводу безопасности здесь, это сценарий).

Затем я хочу создать для него пользовательский интерфейс WPF, подобныйэто:

http://documents.weber.edu/ctctools/sccm/images/osdnotes/9.png

, где часть учетных данных GUI становится отключенной, если пользователь выбирает соединение Workgroup и включается, когда он выбирает соединение Domain (с тем же самымсказал относительно имени фактической рабочей группы / домена, к которому нужно присоединиться).

И я хочу иметь возможность сериализовать / десериализовать эти данные (опять же игнорировать проблемы безопасности, это сценарий).

У меня есть два варианта:

Вариант 1

CreatЭто решение, похожее на:

enum JoinType
{
    Domain,
    Workgroup
}

class NetworkJoin
{
    JoinType JoinType {get; set;}
    string Name {get;set;}
    string Username {get;set;}
    SecureString Password {get;set;}

    void Join()
    {
        // Join code for domain + workgroup
    }
}

Это позволило бы мне легко выполнить TextBoxUsername.IsEnabled = ViewModel.NetworkJoin.JoinType == JoinType.Domain.

Однако, поскольку экземпляр класса сериализован / десериализован, он позволяет экземпляру этого классаиметь JoinType = JoinType.Workgroup, а также Username / Password, и это предположение (хотя и логичное), что то, что нужно сделать для присоединения к сети, основано на проверке JoinType (а не, скажем, * 1044)*)

Что подводит меня к варианту 2

Вариант 2

Что-то похожее на:

interface INetworkJoin
{
    string Name {get;set;}
    void Join();
}

class DomainJoin : INetworkJoin
{
    string Name {get;set;}
    string Username {get;set;}
    SecureString {get;set;}
    void Join()
    {
        // Domain join code
    }
}

class WorkgroupJoin : INetworkJoin
{
    string Name {get;set;}
    void Join()
    {
        // Workgroup join code
    }
}

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

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

Моя ViewModel в основном будет иметь сетевой INJoJoin NetworkJoin, что означает, что мой View будет видеть только INetworkJoin. необходимо , чтобы знать конкретный тип, чтобы определить, показывать или не показывать объекты учетных данных, ему нужно привязать TextBox имени пользователя к свойству Username (которого у INetworkJoin нет ...),то же самое для пароля и т. д.

Резюме

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

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

Любая помощь будет оценена.

Ответы [ 4 ]

0 голосов
/ 28 ноября 2018

В этом случае я бы не связывал пользовательский интерфейс с моделью напрямую.Я хотел бы, чтобы ViewModel имел свои собственные поля для данных.

Большое преимущество этого состоит в том, что, если пользователь меняет свой выбор с «Домен» на «Рабочая группа», а затем обратно на «Домен», выне потеряли имя пользователя и пароль домена, которые они ввели.Но вы все равно можете сохранять целостность своих объектов модели домена (у вас никогда не будет WorkgroupJoin, в котором есть имя пользователя).

Для модели данных я бы следовал вашему варианту 2.viewmodel может взять INetworkJoin в своем конструкторе и извлечь из него данные;а затем позже, когда вы будете готовы к сохранению, вы вызовете метод для модели представления (показанной здесь как GetModel), чтобы создать новый INetworkJoin с введенными пользователем данными.

public class NetworkJoinViewModel : INotifyPropertyChanged
{
    private JoinType _joinType;
    private string _name;
    private string _domainUserName;
    private string _domainPassword;

    public NetworkJoinViewModel(INetworkJoin join) {
        _name = join.Name;
        if (join is DomainJoin domainJoin) {
            _joinType = JoinType.Domain;
            _domainUserName = domainJoin.Username;
            _domainPassword = domainJoin.Password;
        } else if (join is WorkgroupJoin workgroupJoin) {
            _joinType = JoinType.Workgroup;
            _domainUserName = "";
            _domainPassword = "";
        } else throw new ArgumentException("Unknown INetworkJoin implementation");
    }

    public JoinType JoinType {
        get { return _joinType; }
        set {
            _joinType = value;
            NotifyPropertyChanged(nameof(JoinType));
            NotifyPropertyChanged(nameof(IsUserNameEnabled));
            NotifyPropertyChanged(nameof(IsPasswordEnabled));
        }
    }
    public bool IsUserNameEnabled => _joinType == JoinType.Domain;
    public bool IsPasswordEnabled => _joinType == JoinType.Domain;
    // ... typical INPC implementations of Name, UserName, and Password

    public INetworkJoin GetModel() {
        if (_joinType == JoinType.Domain) {
            return new DomainJoin(Name, DomainUserName, DomainPassword);
        } else if (_joinType == JoinType.Workgroup) {
            return new WorkgroupJoin(Name);
        } else throw new InvalidOperationException("Unknown JoinType");
    }
}
0 голосов
/ 28 ноября 2018

Вы должны иметь отдельные доменные модели и просматривать модели.Модели просмотра также обычно реализуют INotifyPropertyChanged и имеют дополнительные свойства, например, для включения / выключения кнопок и т. Д.

Модель домена:

abstract class NetworkJoin
{
    public string Name { get; set; }
}

class WorkgroupJoin : NetworkJoin
{
}

class DomainJoin : NetworkJoin
{
    public string Username { get; set; }
    public SecureString Password { get; set; }
}

Модель просмотра (для простоты я не показываюРеализация INotifyPropertyChanged. Изменения Name, Username и Password должны будут вызвать OnPropertyChanged(nameof(IsOkButtonEnabled))):

class NetworkJoinViewModel
{
    private const int MinPasswordLength = 8;

    public JoinType JoinType { get; set; }
    public string Name { get; set; }
    public string Username { get; set; }
    public SecureString Password { get; set; }

    public bool IsOkButtonEnabled
    {
        get {
            switch (JoinType) {
                case JoinType.Domain:
                    return
                        !String.IsNullOrEmpty(Name) &&
                        !String.IsNullOrEmpty(Username) &&
                        Password != null && Password.Length >= MinPasswordLength;
                case JoinType.Workgroup:
                    return !String.IsNullOrEmpty(Name);
                default:
                    return false;
            }
        }
    }

    public bool IsLoginEnabled => JoinType == JoinType.Domain; // For password an username textboxes.

    public void Join()
    {
        switch (JoinType) { /* ... */  }
    }

    public NetworkJoin ToDomainModel() {
        switch (JoinType) {
            case JoinType.Domain:
                return new DomainJoin {
                    Name = Name,
                    Username = Username,
                    Password = Password
                };
            case JoinType.Workgroup:
                return new WorkgroupJoin {
                    Name = Name
                };
            default:
                return null;
        }
    }
}

Наконец, фабрика модели представления (потому что я не хочу добавлятьToViewModel метод к модели домена. Модель домена не должна знать подробности о моделях представления):

static class NetworkJoinViewModelFactory
{
    public static NetworkJoinViewModel Create(NetworkJoin networkJoin)
    {
        switch (networkJoin) {
            case WorkgroupJoin workgroupJoin:
                return new NetworkJoinViewModel {
                    JoinType = JoinType.Workgroup,
                    Name = workgroupJoin.Name
                };
            case DomainJoin domainJoin:
                return new NetworkJoinViewModel {
                    JoinType = JoinType.Domain,
                    Name = domainJoin.Name,
                    Username = domainJoin.Username,
                    Password = domainJoin.Password
                };
            default:
                return null;
        }
    }
}
0 голосов
/ 28 ноября 2018

Я предлагаю использовать композицию вместо наследования.

Свяжите DomainJoinVM с DomainJoinView и WorkgroupJoinVM с WorkgroupJoinView.Объедините оба представления в NetworkJoinView.

                           NetworkJoinView
                           +      +      +
                          /       |       \
            DomainJoinView        |        WorkgroupJoinView
                 +                |                +
                 |                |                |
                 |                |                |
           DomainJoinVM           |         WorkgroupJoinVM
           +             \        |       /               +
           |              +       |      +                | 
IJoinService               NetworkJoinVM                  IJoinService

Я также предлагаю объявить интерфейс IJoinService для инкапсуляции и абстрагирования слоя модели домена.Затем вы вводите его в представление моделей.Это отделяет модель предметной области от вашей модели представления и позволяет вам при необходимости реализовать постоянство (сериализацию).

Думайте об этом как об отделе и необходимости для разработки пользовательского интерфейса.

public class NetworkJoinVM
{
    public DomainJoinVM DomainJoin; 
    public WorkgroupJoinVM WorkgroupJoin;
    public JoinType JoinWith;

    public NetworkJoinVM(DomainJoinVm domainJoin, WorkgroupJoinVm workgroupJoin)
    {
        DomainJoin = domainJoin;
        WorkgroupJoin = workgroupJoin;
    }

    public void Join()
    {
        if(JoinWith == JoinType.Domain)
            DomainJoin.Join();
        else
            WorkgroupJoin.Join();
    }
}


public class DomainJoinVM
{
    private IJoinService _joinService; 

    public string DomainName;
    public string UserName;
    public string Password;

    public DomainJoinVM(IJoinService joinService)
    {
        _joinService = joinService;
    }

    public void Join()
    {
        _joinService.DomainJoin(DomainName, UserName, Password);
    }
}


public class WorkgroupJoinVM
{
    private IJoinService _joinService;

    public string Name;

    public WorkgroupJoinVM(IJoinService joinService)
    {
        _joinService = joinService;
    }

    public void Join()
    {
        _joinService.WorkgroupJoin(Name);
    }
}


public enum JoinType
{
    Domain,
    Workgroup
}

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

public interface IJoinService
{
    void WorkgroupJoin(string workgroup);
    void DomainJoin(string domain, string username, string password);
}
0 голосов
/ 28 ноября 2018

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

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

Вы можете выдать исключение, если экземпляр INetWorkJoin не является одним из ваших двух типов.

В функциональном программировании есть концепция типа Union, которая идеально подходит для такой ситуации.Есть способы имитировать их в C # с использованием внутренних конструкторов в абстрактном классе.Однако это может не стоить усилий.

Например,

public abstract class NetworkJoin
{
    internal NetworkJoin(){}
    string Name {get;set;}
    void Join();
}

public class DomainJoin : NetworkJoin
{
    public DomainJoin() : base(){}
    string Name {get;set;}
    string Username {get;set;}
    SecureString {get;set;}
    void Join()
    {
        // Domain join code
    }
}

public class WorkgroupJoin : NetworkJoin
{
    public WorkGroupJoin : base(){}
    string Name {get;set;}
    void Join()
    {
        // Workgroup join code
    }
 }

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...