Este artículo proviene de un motor de traducción automática.

El programador políglota

.NET multiparadigmático, Parte 4: Orientación a objetos

Ted Neward

Ted NewardEn el artículo anterior, hemos explorado las similitudes y variabilidad expresado a través de la programación procedural y descubre “ reguladores ” interesantes varios cambios en la que se pueden introducir en diseños. En particular, los dos enfoques de diseño surgido de la línea del procedimiento de pensamiento: cambios de nombre y comportamiento y los cambios en el algoritmo.

Tal como se aumentó la complejidad de los programas y sus requisitos, descubierto a los desarrolladores luchando mantener todos los diversos subsistemas recta. Las abstracciones del procedimiento, descubrimos, no “ escalan ” también es posible que sea ha esperaba. Con la llegada de la interfaz gráfica de usuario, un nuevo estilo de la programación se empezó a aparecer, uno que muchos de los lectores que ha aprendido “ normal antiguo SDK ” reconoce instantáneamente el estilo de la creación de aplicaciones de Windows desde el SDK de Windows 3 y Charles Petzold clásico “ Programming Windows ” (Microsoft Press, 1998). Aparentemente procedimientos por naturaleza, este estilo sigue un patrón de especial interés. Cada procedimiento en un nodo agrupado estrechamente de funcionalidad relacionada que se centra en torno a un parámetro “ controlar ”, la mayor parte a menudo se toma como un parámetro de primer (o único) o devolverla desde una llamada a Create o similares: CreateWindow, FindWindow, ShowWindow y mucho más, todo se centra en torno a un identificador de ventana (HWND), por ejemplo.

Lo que los desarrolladores no darse cuenta en el momento no que éste era realmente una nueva forma de programar, un nuevo paradigma que haría que él mismo pensaban en sólo unos pocos años. En retrospectiva, por supuesto, resulta obvio que se trataba de programación orientado a objetos, y la mayoría de los lectores de esta columna será well-versed con sus preceptos e ideas ya. Dado, ¿por qué debería decidimos emplear pulgadas de columna valioso en el asunto? La respuesta es que ninguna explicación del diseño multiparadigm estaría completa sin la incorporación de objetos dentro de su purview.

Aspectos básicos de objeto

En muchos sentidos, orientación a objetos es un ejercicio de herencia. Herencia de implementación dominado la mayor parte de los análisis de diseño de objeto, con los defensores sugiriendo que abstracciones adecuadas se generan mediante la identificación de las entidades del sistema, los “ sustantivos, ” tal y como estaban y que emerja de su compatibilidad, elevar esa uniformidad en una clase base, por lo que crea una relación de “ IS-A ”. Una persona de IS-A para el alumno, una persona de IS-A del instructor, una persona IS-A objetos y así sucesivamente. Herencia, por tanto, le da a los desarrolladores un eje de nuevo en el que se va a analizar las similitudes y variabilidad.

En los días de C++, el enfoque de la herencia de implementación superado por sí sola, pero a medida que progresaba la hora y la experiencia, herencia de interfaces surgido como alternativa. En esencia, el establecimiento de la herencia de interfaces en el cuadro de herramientas del diseñador permitido para una relación de herencia más clara de peso, declarar que un tipo diferente de IS-A, pero sin el comportamiento o la estructura del elemento primario tipo. Por lo tanto, las interfaces proporcionan un mecanismo para “ agrupación ” de tipos en un eje de herencia sin exigir ninguna restricción en particular en su implementación.

Considere, por ejemplo, en el ejemplo canónico orientado a objetos, de una jerarquía de formas geométricas que se pueden dibujar (si sólo metafóricamente) a la pantalla:

    class Rectangle
    {
      public int Height { get; set; }
      public int Width { get; set; }
      public void Draw() { Console.WriteLine("Rectangle: {0}x{1}", Height, Width); }
    }
    
    class Circle
    {
      public int Radius { get; set; }
      public void Draw() { Console.WriteLine("Circle: {0}r", Radius); }
    }

El punto en común entre las clases, se sugiere que una superclase es en este caso, el orden a evitar la repetición de esa uniformidad en todas las formas geométricas con estas características:

abstract class Shape
{
  public abstract void Draw();
}
  
class Rectangle : Shape
{
  public int Height { get; set; }
  public int Width { get; set; }
  public override void Draw() { 
    Console.WriteLine("Rectangle: {0}x{1}", Height, Width); }
}

class Circle : Shape
{
  public int Radius { get; set; }
  public override void Draw() { Console.WriteLine("Circle: {0}r", Radius); }
  }

Por lo tanto, el momento, por lo tanto, bueno, la mayoría de programadores no necesario no existe ningún problema con lo que se ha hecho hasta ahora. Desafortunadamente, un problema se encuentra in espera la desprevenidos.

Volver a la diversión de Liskov

Las capturas que aquí se conocen como el principio de sustitución de Liskov: cualquier tipo que hereda de otro debe ser completamente intercambiables para que otros. O bien, para utilizar las palabras que originalmente se describen al principio, “ Let q(x) sea una propiedad comprobada acerca de los objetos x de tipo T. A continuación, q(y) debe ser true para y, a objetos de tipo S, donde S es un subtipo de T. ”

Lo que significa que cualquier derivación específica del rectángulo, por ejemplo, una clase Square, debe asegurarse de que obedece a las mismas garantías de comportamientos procede de en la práctica de la base. Un cuadrado es esencialmente un rectángulo con la garantía de que tanto el alto y ancho son siempre iguales, por lo que parece razonable escribir el cuadrado como en el ejemplo de de figura 1.

La figura 1 derivar un cuadrado

class Rectangle : Shape
{
  public virtual int Height { get; set; }
  public virtual int Width { get; set; }
  public override void Draw() { 
    Console.WriteLine("Rectangle: {0}x{1}", Height, Width); }
}

class Square : Rectangle
{
  private int height;
  private int width;
  public override int Height { 
    get { return height; } 
    set { Height = value; Width = Height; } 
  }
  public override int Width {
    get { return width; }
    set { Width = value; Height = Width; }
  }
}

Observe cómo las propiedades Height y Width ahora virtuales para evitar cualquier tipo de sombreado o el comportamiento de creación de divisiones al reemplazar el método de la clase Square accidentales. Hasta ahora es tan buena.

A continuación, se pasa un cuadrado en un método que toma un rectángulo y “ crece ” que (a veces, ¿qué geeks gráficos a una transformación de “ ”):

class Program
{
  static void Grow(Rectangle r)
  {
    r.Width = r.Width + 1;
    r.Height = r.Height + 1;
  }

  static void Main(string[] args)
  {
    Square s = new Square();
    s.Draw();
    Grow(s);
    s.Draw();
  }
}

La figura 2 muestra el resultado de llamar a este código, que es no lo que cabría esperar.

Figure 2 A Surprising Result with Grow Code

Figura 2 un sorprendente resultado con el código de crecimiento

El problema, como el cuidado de los lectores que han supuso ya, es que se supone que cada implementación de las propiedades se llama por separado y, por tanto, debe actuar de forma independiente para garantizar que el alto == de restricción de ancho alrededor de cuadrado en todo momento. El código de crecimiento, sin embargo, se supone que se pasa en un rectángulo, y permanece totalmente ignorant el hecho de que es un cuadrado que se incluyen en (como los destinados a!) y actúa de forma totalmente adecuado de rectángulos.

¿El núcleo del problema? Cuadrados no aparecen los rectángulos. Tienen una gran cantidad de similitud, de acuerdo, pero al final del día, no mantenga las restricciones de un cuadrado de rectángulos (que también es cierto, por cierto, elipses y círculos) y tratar de modelo de la otra se basa un fallacy. Resulta tentador heredan el cuadrado de rectángulo, en particular a que nos permite reutilizar código, pero se trata de una premisa es false. De hecho, incluso irá hasta el momento que se sugiere que uno nunca debe utilizar la herencia para fomentar la reutilización hasta el principio de sustitución de Liskov para dos de los tipos se ha demostrado para ser true.

En este ejemplo no es nuevo, Robert “ tío Bob ” Martin (bit.ly/4F2R6t ) describe Liskov y en este ejemplo exacto en el medio de años 90, cuando habla con los desarrolladores de C++. Parcialmente mediante el uso de interfaces para describir las relaciones se pueden resolver algunos problemas como éste, pero que no ayuda en este caso particular, ya que el alto y ancho permanecen aparte de las propiedades.

¿Hay una solución en este caso? No exactamente, aunque no mantiene la relación de cuadrado-como-derivada-de-rectángulo en su lugar. La mejor respuesta viene realicen cuadrado directa descendiente de la forma y abandonar el enfoque de herencia por completo:

 

class Square : Shape
{
  public int Edge { get; set; }
    public override void Draw() { Console.WriteLine("Square: {0}x{1}", Edge, Edge); }
}

class Program
{
    static void Main(string[] args)
    {
      Square s = new Square() { Edge = 2 };
      s.Draw();
      Grow(s);
      s.Draw();
    }
}

Por supuesto, ahora tenemos el problema que cuadrado no se puede pasar en para aumentar en absoluto, y parece que hay una relación potencial de reutilización de código no existe. Podemos resolver esto en un sentido, ya que proporciona una vista del cuadrado como un rectángulo mediante una operación de conversión, como se muestra en de figura 3.

Operación de conversión de la figura 3

class Square : Shape
{
  public int Edge { get; set; }
  public Rectangle AsRectangle() { 
    return new Rectangle { Height = this.Edge, Width = this.Edge }; 
  }
  public override void Draw() { Console.WriteLine("Square: {0}x{1}", Edge, Edge); }
}

class Program
{
  static void Grow(Rectangle r)
  {
    r.Width = r.Width + 1;
    r.Height = r.Height + 1;
  }

  static void Main(string[] args)
  {
    Square s = new Square() { Edge = 2 };
    s.Draw();
    Grow(s.AsRectangle());
    s.Draw();
  }
}

Funciona, pero es un poco complicado. Puede también utilizamos la función de operador de conversión de C# para que sea más fácil convertir cuadrados para rectángulos, tal como se muestra en de figura 4.

Figura 4 el mecanismo de operador de conversión de C#

class Square : Shape
{
  public int Edge { get; set; }
  public static implicit operator Rectangle(Square s) { 
    return new Rectangle { Height = s.Edge, Width = s.Edge }; 
  }
  public override void Draw() { Console.WriteLine("Square: {0}x{1}", Edge, Edge); }
}

class Program
{
  static void Grow(Rectangle r)
  {
    r.Width = r.Width + 1;
    r.Height = r.Height + 1;
  }

  static void Main(string[] args)
  {
    Square s = new Square() { Edge = 2 };
    s.Draw();
    Grow(s);
    s.Draw();
  }
}

Este enfoque, aunque quizás brusca distinto del esperado, ofrece la misma perspectiva del cliente como antes, pero sin los problemas de la implementación anterior, como se muestra en de figura 5.

Figure 5 The Result of Using the C# Conversion Operator Facility

La figura 5 Resultado del uso de la función de operador de conversión de C#

En realidad, tenemos un problema diferente: donde antes de que el método Grow modifica el rectángulo que se pasa, ahora parece que está haciendo nada, en gran medida porque lo está modificando una copia de la cuadrado, no el original cuadrado por sí mismo. Se ha podido solucionar haciendo que la devolución del operador de conversión de una nueva subclase del rectángulo que contiene una referencia secreta a esta instancia Square, para que las modificaciones realizadas en las propiedades Height y Width a su vez se vuelve y modificar borde del cuadrado... pero, a continuación, nos encontramos al problema original.

No feliz fin aquí

En Hollywood, las películas tienen que finalizar de forma acorde con las expectativas de la audiencia o rechazados en la oficina del cuadro se enfrentan. Yo no soy un moviemaker, por lo que me no siento ninguna compulsion para presentar los lectores de esta columna con un final feliz en todos los casos. Se trata de uno de estos casos: intentar mantener el código original en su lugar y que todo funcione sólo crea más profundos y más ataques. Las soluciones pueden desplazarse el método Grow o transformar directamente a la jerarquía de la forma de realizar simplemente el método Grow devolver el objeto modificado en vez de modificar el objeto pasado en (que es algo que hablaremos en otra columna), pero en resumen, no se puede mantener el código original en su lugar y todo lo que funciona correctamente mantiene.

Todo esto está diseñado para mostrar con precisión una cosa: los programadores orientados a objetos se son cómodos con la definición de modelos similitudes y variabilidad con herencia, quizás demasiado mucho por lo tanto. Recuerde que si decide utilizar el eje de herencia para capturar su compatibilidad, tiene que asegurarse de que esta compatibilidad se mantiene a través de la jerarquía completa si para evitarse errores sutiles, como la siguiente.

Recuerde también que la herencia es siempre una variabilidad positiva (agregar nuevos campos o comportamientos), y que la variabilidad negativa en la herencia (que es lo que cuadrado intentó hacer) de la definición de modelos es casi siempre una receta para desastres a lo largo de las líneas de Liskovian. Asegúrese de que todas las relaciones basadas en la herencia implican una uniformidad positivo y lo debe ser una buena. ¡ Feliz codificación!

Ted Neward es un principal con Neward & Associates, un independiente especializado en sistemas de plataforma de .NET Framework y Java de empresa. Ha escrito más de 100 artículos, es un ponente de MVP de C# y de INETA y ha creado y coautor de libros de una docena, como “ Professional F # 2.0 ” (Wrox, 2010). También consulta y mentors con regularidad. Ponerse en ted@tedneward.com con preguntas o solicitudes de consultoría y leer su blog en blogs.tedneward.com de .

Gracias al siguiente experto técnico por su ayuda en la revisión de este artículo: Anthony Green