Как сделать общий метод двум классам C#

313
29 сентября 2017, 15:48

У нас есть общий метод

FindPerimetr()

для классов Rectangle и Square. Как реализовать так чтобы не писать метод два раза в разных классах? Слышал что то про родительский класс это оно?

Answer 1

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

public abstract class Polygon
{
    public abstract double FindPerimetr();
}
public class Square : Polygon
{
    public double Size { get; set; }
    public override double FindPerimetr()
    {
        return Size * 4;
    }
}
public class Rectangle : Polygon
{
    public double Width { get; set; }
    public double Height { get; set; }
    public override double FindPerimetr()
    {
        return (Width + Height) * 2;
    }
}

Вариант с интерфейсом вместо abstract class тоже подойдет.

Вам будут советовать унаследовать квадрат от прямоугольника - но такое наследование нарушит принцип подстановки Барбары Лисков. А нарушать принципы из списка SOLID нехорошо. Например, вы скажете "квадрат - это такой прямоугольник, у которого ширина равна высоте". И напишете вот такой код:

public class Square : Rectangle
{
    public override double Width { get => base.Width; set => base.Width = base.Height = value; }
    public override double Height { get => base.Height; set => Width = value; }
}
public class Rectangle
{
    public virtual double Width { get; set; }
    public virtual double Height { get; set; }
    public double FindPerimetr()
    {
        return (Width + Height) * 2;
    }
}

Плохой препод по ООП скажет "молодец, ты проявил знание наследования!". Хороший препод по ООП скажет: Смотри, у нас был вот такой метод Stretch(Rectangle r), который растягивал прямоугольник в 2 раза:

static void Stretch(Rectangle r)
{
    r.Height *= 2;
    r.Width *= 2;
}

И этот метод отлично работает до тех пор, пока не в него не попадет квадрат. А квадрат он, внезапно, сделает длинее и шире в 4 раза.

Answer 2

Добавлю к ответу PashaPash еще два способа унаследовать квадрат от прямоугольника, которые не нарушают LSP.

Первый способ - можно сделать их неизменяемыми:

public class Rectangle
{
    public double Width { get; }
    public double Height { get; }
    public Rectangle (double w, double h)
    {
        Width = w;
        Height = h;
    }
    public double FindPerimetr() => (Width + Height) * 2;
}
public class Square : Rectangle 
{
    public Square (double size) : base(size, size) { }
}

Способ второй - оставить их изменяемыми, но не давать прямого контроля над значениями свойств:

public class Rectangle
{
    public double Width { get; protected set; }
    public double Height { get; protected set; }
    public Rectangle (double w, double h)
    {
        Width = w;
        Height = h;
    }
    public double FindPerimetr() => (Width + Height) * 2;
    public void Stretch(double s)
    {
        Width *= s;
        Height *= s;
    }
}
public class Square : Rectangle 
{
    public Square (double size) : base(size, size) { }
}

Но надо заметить, что второй пример на самом деле соблюдает LSP ценой жертвы OCP (принципа открытости-закрытости): любая операция, добавленная в базовый класс, должна учитывать все его подклассы, а неучтенные заранее подклассы оказываются при таком подходе запрещены.

К примеру, если бы в класс Rectangle был добавлен метод void Stretch(double sx, double sy) - то его наличие уже не позволило бы унаследовать класс Square от Rectangle.

Тем не менее, OCP не так важен как LSP - и при построении, к примеру, DSL (языков специфичных для предметной области) такой подход может быть оправдан.

Answer 3

Для любой плоской фигуры из 4-х точек периметром будет сумма длин отрезков их соединяющих. Если расчет периметра реализовать в абстрактном классе - реализовывать в наследниках не потребуется.

using System;
namespace Perimeter
{
    public class Point
    {
        public double X { get; set; }
        public double Y { get; set; }
        public Point(double X, double Y)
        {
            this.X = X;
            this.Y = Y;
        }
    }
    public abstract class Tetragon
    {
        public Point Vertex1 { get; set; }
        public Point Vertex2 { get; set; }
        public Point Vertex3 { get; set; }
        public Point Vertex4 { get; set; }
        public Tetragon(Point Vertex1, Point Vertex2, Point Vertex3, Point Vertex4)
        {
            this.Vertex1 = Vertex1;
            this.Vertex2 = Vertex2;
            this.Vertex3 = Vertex3;
            this.Vertex4 = Vertex4;
        }
        public double FindPerimetr()
        {
            double Length1 = Math.Sqrt(Math.Pow(Vertex2.X - Vertex1.X, 2) + Math.Pow(Vertex2.Y - Vertex1.Y, 2));
            double Length2 = Math.Sqrt(Math.Pow(Vertex3.X - Vertex2.X, 2) + Math.Pow(Vertex3.Y - Vertex2.Y, 2));
            double Length3 = Math.Sqrt(Math.Pow(Vertex4.X - Vertex3.X, 2) + Math.Pow(Vertex4.Y - Vertex3.Y, 2));
            double Length4 = Math.Sqrt(Math.Pow(Vertex1.X - Vertex4.X, 2) + Math.Pow(Vertex1.Y - Vertex4.Y, 2));
            return Length1 + Length2 + Length3 + Length4;
        }
    }
    public class Rectangle : Tetragon
    {
        public Rectangle(double width, double height)
            : base(new Point(0, 0), new Point(0, height), new Point(width, height), new Point(width, 0))
        { }
    }
    public class Square : Tetragon
    {
        public Square(double size)
            : base(new Point(0, 0), new Point(0, size), new Point(size, size), new Point(size, 0))
        { }
    }
    class Program
    {
        static void Main(string[] args)
        {
            // 10
            var rec = new Rectangle(2, 3);
            Console.WriteLine(string.Format("Perimeter of rectangle: {0}", rec.FindPerimetr()));
            // 4
            var square = new Square(1);
            Console.WriteLine(string.Format("Perimeter of square: {0}", square.FindPerimetr()));
            Console.ReadLine();
        }
    }
}
Answer 4

Вариантов много. Незнаю нужно ли учить плохому , но я бы выделил:

  1. "Дружбу классов".
  2. Рефлексию.
  3. Наследование. Можно обьявить в базовом классе функцию, и реализовать её в других классах (рекомендуеться)
  4. Можно создать интерфейс "Периметр" и вычислять через наследование+интерфейс
  5. В базовом классе можно создать массив "стороны", который заполнять при создании класса, в массив можно добавлять дубликаты полей для правильного счёта.

Покажу из них два.

  1. "дружбу классов"

    public double FindPerimetr() {  
      if (this is Square) return ((Square)this).Size *4;
      if (this is Rectangle)
        return (((Rectangle)this).Width +    ((Rectangle)this).Height) * 2;
      return 0; // неизвесно что
    

    }

Недостаток, тот кто будет наследовать ваш класс - будет недоволен что его периметр не считается. Если "правильно" расставить if-последовательность и одинаково назвать одну из сторон - можно обойтись двумя классами.

  1. Можно "схитрить" через рефлексию

    using System.Reflection;     // рефлексия
    using System.ComponentModel; // тут AmbientValue 
     public class Polygon {
    public double FindPerimetr() {
         double ret = 0;
         foreach (PropertyInfo item in this.GetType().GetProperties())
          if (item.PropertyType == typeof(double)) // Проверки
              ret += (double) item.GetValue(this,null);
          return ret;
          }
       }
    

Метод будет работать если каждая сторона "присутствует". Если нужно множители - их нужно где-то прописать, например в атрибутах.

Например так:

 public class Polygon {
  public double FindPerimetr() {
     double ret = 0;
     foreach (PropertyInfo item in this.GetType().GetProperties())
      if (item.PropertyType == typeof(double)) // Проверки 
          {
          object[] attr = item.GetCustomAttributes(typeof(AmbientValueAttrubute,false);
          ret += ((double) item.GetValue(this,null)) 
            * ((attr.Length==0)?0: (double) ((AmbientValueAttrubute)attr[0]).Value);
          }
      return ret;
      }
   }
public class Rectangle : Polygon  {
  [AmbientValue(4)]
  public double Size { get; set;}
  }

Но лучше для атрибута создать свой класс на базе Attribute. Рефлексия не считается хорошим тоном, но как вариант. Она сложная, чуть замедляет работу и плохочитабельна. Про доп-атрибут тому кто будет продолжать дорабоатывать вашу библиотеку прийдётся тоже догадываться.

А вот стоит ли "овчина выделки" - судите сами.

READ ALSO
Разбавить строку точками c#

Разбавить строку точками c#

Собственно есть строка "stackoverflow", нужно её разбавить точками(подскажите алгоритм), вот пример:

307
Сортировка списка с учетом регистра list.Sort() - C#

Сортировка списка с учетом регистра list.Sort() - C#

Сортировка списка с учетом регистра listSort() - C#

247
Создание автообновления

Создание автообновления

Я пытался создать автообновление своей программы через zip-архивДля этого использую DotNetZip (Ionic

228
C# Скачивание изображения из документа по url на litres.ru (Режим только чтение)

C# Скачивание изображения из документа по url на litres.ru (Режим только чтение)

Здравствуйте! Недавно столкнулся с проблемой скачивания картинки с сайта litresru

257