C # Implementazione di più interfacce disparate in una singola classe base

0

(Questo è probabilmente un dupe, ma in tal caso non l'ho ancora visto, anche se potrebbe essere Buone pratiche per incapsulare un parametro che richiede l'implementazione di più interfacce , ma quella domanda non risponde alla mia situazione)

Normalmente non mi interessa che c # abbia solo ereditarietà singola, ma mi sono imbattuto in qualcosa in cui penso che l'ereditarietà multipla in realtà migliorerebbe le cose, e non riesco a vedere un modo pulito per farlo con una singola eredità.

Questo deriva dal voler supportare sia INotifyPropertyChanged che INotifyDataErrorInfo in modo riutilizzabile. Queste interfacce sono indipendenti e richiedono una quantità non trascurabile di codice per supportarle, e fino ad ora ho considerato solo INotifyPropertyChanged e ho un sistema di:

public class MyPropertyChangedBase : INotifyPropertyChanged
{
   // re-usable code to support INotifyPropertyChanged
}

public MyViewModel : MyPropertyChangedBase
{
  // various WPF bound properties that notify the UI on change
}

Tuttavia, ora voglio implementare INotifyDataErrorInfo in modo riutilizzabile, ma il codice per supportare questa interfaccia è totalmente ortogonale e non correlato al codice INotifyPropertyChanged . Quindi idealmente dovrebbero essere implementati in classi base non correlate e utilizzare l'ereditarietà multipla per il mio modello di vista. EG (nota che questo NON è codice c # valido - è solo per evidenziare una situazione 'idealistica'):

public class MyPropertyChangedBase : INotifyPropertyChanged
{
   // re-usable code to support INotifyPropertyChanged
}

public class MyErrorChangedBase : INotifyDataErrorInfo
{
   // re-usable code to support INotifyDataErrorInfo
}

public MyViewModel : MyPropertyChangedBase, MyErrorChangedBase
{
  // various WPF bound properties that notify the UI on change and error
}

(Si noti che non posso usare la composizione per risolvere il problema, poiché dovrei mappare dai componenti alle istanze pubbliche degli elementi dell'interfaccia, che sposta semplicemente il problema e non lo risolve)

Quindi mi sembra che il codice INotifyPropertyChanged si basi sul codice INotifyDataErrorInfo o viceversa. EG

public class MyPropertyChangedBase : INotifyPropertyChanged
{
   // re-usable code to support INotifyPropertyChanged
}

public class MyPropertyAndErrorChangedBase : MyPropertyChangedBase , INotifyDataErrorInfo
{
   // re-usable code to support INotifyDataErrorInfo
}

public MyViewModel : MyPropertyAndErrorChangedBase
{
  // various WPF bound properties that notify the UI on change and error
}

Questo affidamento di una classe indipendente a un'altra non mi sta bene. Quindi c'è un modo più pulito per farlo? O sono bloccato con esso?

Infine, riconosco che realisticamente non implementeresti INotifyDataErrorInfo senza aver prima implementato INotifyPropertyChanged , quindi la dipendenza di una classe dall'altra potrebbe essere un compromesso pragmatico. Ma sto ancora cercando di trovare il modo più pulito per farlo.

    
posta Peter M 09.11.2017 - 14:43
fonte

3 risposte

0

Come Greg ha già menzionato, le tue due interfacce si riferiscono alla vista. Più specificamente, entrambi riguardano le interazioni ViewModel<->View . Quindi, avere una implementazione di classe non è un grosso problema.

Tuttavia, capisco che potresti voler evolvere il comportamento di ogni implementazione dell'interfaccia in modo indipendente, quindi che ne dici di questo approccio basato sulla composizione:

public abstract class MyPropertyChangedBase : INotifyPropertyChanged
{
    // re-usable code to support INotifyPropertyChanged
}

public abstract class MyErrorChangedBase : INotifyDataErrorInfo
{
    // re-usable code to support INotifyDataErrorInfo
}

public abstract class ViewModelBase : MyPropertyChangedBase
{
    // classic VM
}

public abstract class ErrorAwareViewModelBase : INotifyPropertyChanged, INotifyDataErrorInfo
{
    private readonly MyPropertyChangedBase changeNotifier;
    private readonly MyErrorChangedBase errorNotifier;

    public ErrorAwareViewModelBase(MyPropertyChangedBase changeNotifier, MyErrorChangedBase errorNotifier)
    {
        this.changeNotifier = changeNotifier;
        this.errorNotifier = errorNotifier;
    }

    event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
    {
        add
        {
            this.changeNotifier.PropertyChanged += value;
        }

        remove
        {
            this.changeNotifier.PropertyChanged -= value;
        }
    }

    bool INotifyDataErrorInfo.HasErrors => this.errorNotifier.HasErrors;

    event EventHandler<DataErrorsChangedEventArgs> INotifyDataErrorInfo.ErrorsChanged
    {
        add
        {
            this.errorNotifier.ErrorsChanged += value;
        }

        remove
        {
            this.errorNotifier.ErrorsChanged -= value;
        }
    }

    IEnumerable INotifyDataErrorInfo.GetErrors(string propertyName)
    {
        return this.errorNotifier.GetErrors(propertyName);
    }
}
    
risposta data 09.11.2017 - 15:11
fonte
1

Non vedo assolutamente alcun motivo per cui implementare sia INotifyPropertyChanged che INotifyDataErrorInfo su una classe genitore è una cattiva progettazione. Entrambe le interfacce si occupano della vista. Queste cose sono strettamente accoppiate comunque. Non ottieni altro che il dolore della manutenzione separandoli in classi diverse, soprattutto se significa esporre uno stato privato o protetto in un contesto pubblico al fine di ottenere questa separazione.

Separazione delle preoccupazioni che richiede la rottura L'incapsulamento finisce per essere una dispersione di preoccupazioni, perché qualsiasi codice arbitrario può cambiare quello stato privato.

Vai con una classe base che implementa due interfacce.

Saltando da C # e dalla struttura di Windows Presentation Foundation, ho visto una serie di esempi di codice in Java in cui un oggetto di accesso ai dati implementa due interfacce diverse:

public interface BlogDataReader {
    Blog find(int blogId);
}

public interface BlogDataWriter {
    void create(Blog blog);
    void update(Blog blog);
}

public interface BlogDataDeleter {
    void remove(Blog blog);
}

public class BlogDataAccess implements BlogDataReader, BlogDataWriter, BlogDataDeleter {
    public Blog find(int blogId) {
        // ...
    }

    public void create(Blog blog) {
        // ...
    }

    public void update(Blog blog) {
        // ...
    }

    public void remove(Blog blog) {
        // ...
    }
}

Una classe, o una classe base, che implementa più interfacce correlate non è un anti-pattern o una cattiva progettazione.

    
risposta data 09.11.2017 - 14:50
fonte
0

In generale, penso che la mia pugnalata sarebbe con i metodi di estensione o le classi di aiuto. Piuttosto che inserire il codice in una classe base, spostalo nei metodi di estensione. Qualcosa come il seguente:

public static class INotifyPropertyChangedExtensions
{
    public static ThingToImplementFirstNotifyMethod(INotifyPropertyChanged this myObj)
    {
        /...
    }
}
public static class INotifyDataErrorInfoExtensions
{
    public static ThingToImplementFirstErrorMethod(INotifyPropertyChanged this myObj)
    {
        /...
    }
}

allora nella tua classe fai qualcosa di simile a questo:

public class MyClass : INotifyPropertyChanged, INotifyDataErrorInfo
{
    public void INotifyPropertyChangedFirstMethod()
    {
        this.ThingToImplementFirstNotifyMethod()
    }
    public void INotifyDataErrorInfoFirstMethod()
    {
        this.ThingToImplementFirstErrorMethod()
    }
}

Se il codice richiesto per implementare l'interfaccia richiede più stato, una classe helper e una composizione potrebbero essere migliori:

public class INotifyPropertyChangedHelper
{
    private int state;
    public ThingToImplementFirstNotifyMethod(INotifyPropertyChanged this myObj)
    {
        /...
    }
}
public class INotifyDataErrorInfoHelper
{
    private int state;
    public ThingToImplementFirstErrorMethod(INotifyPropertyChanged this myObj)
    {
        /...
    }
}

e la tua classe sarebbe simile a questa:

public class MyClass : INotifyPropertyChanged, INotifyDataErrorInfo
{
    private INotifyPropertyChangedHelper _notifyHelper;
    private INotifyDataErrorInfoHelper _errorHelper;
    public MyClass()
    {
        _notifyHelper = new INotifyPropertyChangedHelper();
        _errorHelper = new INotifyDataErrorInfoHelper()
    }
    public void INotifyPropertyChangedFirstMethod()
    {
        _notifyHelper.ThingToImplementFirstNotifyMethod()
    }
    public void INotifyDataErrorInfoFirstMethod()
    {
        _errorHelper.ThingToImplementFirstErrorMethod()
    }
}

Se le interfacce sono strettamente correlate, fai qualcosa di simile a ciò che @GregBurghardt suggerisce che funzionerebbe pure.

    
risposta data 09.11.2017 - 15:21
fonte

Leggi altre domande sui tag