I have 2 twin classes representing “the same” objects: one defined for computational evaluations and the other for the UI binding. I would like to reduce the duplicated code and errors. I’ve defined a common interface and moved all the shared static methods by using Extension methods.
What is missing is how to define properties validation in just one place.
I’m currently using System.ComponentModel.DataAnnotations
but I need to define the same annotations on both classes. In the base class I use DataAnnotation to check JSON deserialization while the same values are used in the UI.
I can not use inheritance because the UI class is already inheriting from a ViewModel
class and multiple inheritance is not allowed.
Any other approach or idea?
Here a project with the same basic concept issue.
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Runtime.CompilerServices;
namespace myRefactoring
{
public interface IShape
{
// would be nice to define the DataAnnotation here
// but it's not working with interfaces
public int N { get; set; }
public double side { get; set; }
public double perimeter { get; }
public double area { get; }
}
public class Shape : IShape
{
/// <summary>
/// main class for math evaluations
/// </summary>
[Range(3, int.MaxValue, ErrorMessage = "at least 3 sides")]
public int N { get; set; }
[Range(double.Epsilon, double.MaxValue, ErrorMessage = "just positive size")]
public double side { get; set; }
public Shape(int N, double S) { this.N = N; this.side = S; }
public double perimeter => ShapeExtension.perimeter(this);
public double area => ShapeExtension.area(this);
}
public class ShapeUI : ViewModel, IShape, INotifyPropertyChanged
{
/// <summary>
/// class for UI binding
/// NotifyPropertyChanged ...
/// </summary>
public event PropertyChangedEventHandler? PropertyChanged;
public ShapeUI(int N, double S) { this._N = N; this._side = S; }
private int _N;
[Range(3, int.MaxValue, ErrorMessage = "at least 3 sides")]
public int N {
get => _N;
set {
_N = value;
NotifyPropertyChanged();
NotifyPropertyChanged(nameof(perimeter));
NotifyPropertyChanged(nameof(area));
}
}
private double _side;
[Range(double.Epsilon, double.MaxValue, ErrorMessage = "just positive size")]
public double side {
get => _side;
set {
if (value != this._side)
{
this._side = value;
NotifyPropertyChanged();
NotifyPropertyChanged(nameof(perimeter));
NotifyPropertyChanged(nameof(area));
}
}
}
public double perimeter => ShapeExtension.perimeter(this);
public double area => ShapeExtension.area(this);
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
Console.WriteLine("changing " + propertyName);
}
}
public static class ShapeExtension
{
public static double perimeter(this IShape s) => s.N * s.side;
public static double area(this IShape s) => Math.Pow(s.side / 2, 2) / Math.Tan(Math.PI / s.N) * s.N;
}
public class ViewModel
{
// class implementing ViewModel methods and variables
}
internal class Program
{
static void Main(string[] ars)
{
Console.WriteLine("Create triangle");
Shape triangle = new(3, 1.0);
Console.WriteLine($"area = {triangle.area:F3}");
Console.WriteLine("nnCreate square");
ShapeUI square = new(4, 1.0);
Console.WriteLine($"area = {square.area:F3}");
Console.WriteLine("nnModify square");
square.side = 2.0;
Console.WriteLine($"area = {square.area:F3}");
}
}
}