2017-06-21 5 views
2

J'essaie de comprendre la relation is-a vs is-like-a où je lis quelque part que nous devons essayer de suivre le design de telle sorte que nous ayons toujours une relation is-a et non- comme un. Prenons l'exemple classique de la classe de base de forme et des classes triangulaires et circulaires dérivées. donc le cercle est-une part et le triangle est-une forme. La zone d'affichage de la fonction a été définie dans la classe de base. Maintenant, le programme ci-dessous fonctionne bien.conception de classe de forme avec cercle et triangle

#include "stdafx.h" 
#include<cmath> 
#include <iostream> 

class shape 
{ 
public: 
    virtual void displayArea()=0; 
}; 


class circle :public shape 
{ 
    int radius; 
public: 
    circle(int radius2) :radius(radius2){ } 
    void displayArea() 
    { 
     double area = 3.14*radius*radius; 
     std::cout << " \n Area circle" << area<<std::endl; 
    } 
}; 

class triangle :public shape 
{ 
    double a,b,c; 
public: 
    triangle(double a1, double b1, double c1): a(a1), b(b1),c(c1) 
    { 
     if (a + b > c && a + c > b && b + c > a) 
      std::cout << "The sides form a triangle" << std::endl; 
     else 
      std::cout << "The sides do not form a triangle. Correct me !" << std::endl; 

    } 

    void displayArea() 
    { 


     double s = (a + b + c)/2; 
     double area = sqrt(s*(s - a)*(s - b)*(s - c)); 
     std::cout << " \n Area triangle"<< area<<std::endl; 
    } 
}; 

void main() 
{ 
    shape * p1[2]; 
    p1[0]= new circle(20); 

    p1[1] = new triangle(5.6,8.1,10.3); 
    for (int i = 0; i < 2; ++i) 
    { 
     p1[i]->displayArea(); 
    } 

    int y; 
    std::cin >> y; 
} 

Maintenant, si l'exigence est que l'on doit mettre en œuvre la fonction modifyShape où chaque paramètre de la forme est modifiée en fonction des paramètres de l'utilisateur alors comment dois-je modifier mes classes telles que mon est-une relation n'est pas modifiée . Quand je le regarde, je pense que je vais devoir définir un seul argument modifyShape en cercle et un argument de 3 arguments modifyShape en triangle. Mais comment cette fonction devrait-elle ressembler dans la classe de base?

Option 1: Je définis à la fois la fonction modifyShape à la fois en argument simple et en argument, ce qui signifierait que j'aurais une fonction supplémentaire de 2 arguments en cercle et une fonction supplémentaire de 1 argument en triangle.

Option 2: Je définis une fonction d'argument variable modifyShape en forme mais d'une manière ou d'une autre cela ne me semble pas plus propre.

+0

Je ne l'ai jamais entendu parler est-comme-un. Comment définiriez-vous cette relation? –

+0

"fonction modifyShape où chaque paramètre de la forme est modifié en fonction du paramètre de l'utilisateur" Cela ne ressemble pas à une exigence bien indiquée. Qu'est-ce que "basé sur le paramètre de l'utilisateur" signifie exactement? Si l'utilisateur entre 5, et que nous avons un triangle unitaire en (7, 8) et un cercle d'unité centré en (1, -2), quel est le résultat attendu? –

Répondre

0

Il existe une troisième option que vous pouvez utiliser, vous pouvez créer une nouvelle hiérarchie de classes (ou structs) qui représentera les paramètres de chaque forme. Vous pouvez ensuite passer le pointeur à la classe de base en tant qu'argument de la fonction virtuelle. Par exemple:

struct ShapeParams 
{ 
    ... 
} 

struct TriangleParams : public ShapeParams 
{ 
    double a; 
    double b; 
    double c: 
} 
class shape 
{ 
    public: 
    virtual void displayArea()=0; 
    modifyShape (ShapeParams*) = 0; 
}; 

class triangle :public shape 
{ 
    public: 
    void modifyShape (ShapeParams*) = override; 

    private: 
    TriangleParams m_params; 
} 
+0

Ceci est un mauvais conseil. L'implémentation modifyShape doit convertir le paramètre ShapeParams. C'est sujet aux erreurs, et vous ne gagnez rien. Vous devrez toujours passer TriangleParams à un triangle. –

+0

Je vais en effet avoir besoin d'un casting, mais cela sera fait dans une portée qui fonctionne toujours avec le même type de ShapeParams. Cela peut être dangereux si vous passez un pointeur vers une autre classe, mais cela peut être dit à peu près n'importe quoi. Ce que vous gagnez de cette façon est un appel virtuel qui peut être utilisé pour généraliser le travail avec des formes. Peut être utile par exemple si vous utilisez Modèle de conception de méthode de modèle. – user8160628

0

Vous pouvez vous restructurer les classes un peu, mais il faudrait une autre classe indépendante. Vous pouvez créer un ensemble de classes de vecteurs mathématiques 2D et 3D, mais vous devez avoir tous les opérateurs surchargés et les fonctions mathématiques que les vecteurs peuvent faire comme ajouter, soustraire, multiplier par vecteur ou par scalaire et si par vecteur vous avez le point et le produit croisé à s'inquiéter. Vous auriez besoin de normaliser les méthodes, la longueur et autres. Une fois que vous avez ces classes de vecteur de mathématiques de travail. Ensuite, vous pouvez redessiner vos classes de formes en utilisant les vecteurs à la place. Ou bien, au lieu d'écrire votre propre classe de vecteurs, vous pouvez utiliser une classe de bibliothèque mathématique telle que la bibliothèque mathématique de GLM utilisée pour travailler dans OpenGL. C'est gratuit et open source et c'est aussi une bibliothèque en-tête seulement. Une fois que vous avez installé la bibliothèque sur un chemin, tout ce que vous devez faire est d'inclure son en-tête. Vous n'avez pas à vous soucier de la liaison. Puis, avec ces classes de vecteurs, il ferait le calcul dans vos classes de forme plus facile à faire, et il serait plus facile de concevoir les classes de forme: Voici un exemple de ce code de pseudo ressemblerait à ceci:

#include <glm\glm.hpp> 
// Needed If Doing Matrix Transformations: Rotation, Translation Scaling etc. 
// #include <glm\gtc\matrix_transform.hpp> 

class Shape { 
public: 
    enum Type { 
     NONE = 0, 
     TRIANGLE, 
     SQUARE, 
     CIRCLE, 
    }; 
protected: 
    Type type_; 
    glm::vec4 color_ { 1.0f, 1.0f, 1.0f, 1.0f }; // Initialize List Set To White By Default 
    double perimeter_; // Also Circumference for Circle 
    double area_;  
    // double volume_; // If in 3D. 
public: 
    // Default Constructor 
    Shape() : type_(NONE), color_(glm::vec4(1.0f, 1.0f, 1.0f, 1.0f)) {}  
    // User Defined Constructors 
    // Sets Shape Type Only Color Is Optional & By Default Is White 
    explicit Shape(Type type, glm::vec4 color = glm::vec4()) : type_(type), color_(color) {} 

    Type getType() const { return type_; } 
    void setType(Shape::Type type) { 
     if (type_ == NONE) { 
      // Its okay to set a new shape type 
      type_ = type; 
      } 

      // We Already Have a Defined Shape 
      return; 
     } 

     // Getters That Are Commonly Found Across All Shapes 
     double getPerimeter() const { return perimeter_; } 
     double getArea() const { return area_; } 

     // Common Functions that can be done to any shape 
     void setSolidColor(glm::vec4 color) { color_ = color }; 
     glm::vec4 getColor() const { return color; } 

     // Common Interface That All Shapes Share But Must Override 
     virtual double calculateArea() = 0; 
     virtual double calculatePerimeter() = 0; 

     // Since we do not know what kind of shape to modify until we have one 
     // to work with, we do not know how many parameters this function will need. 
     // To get around this we can use a function template and then have overloads 
     // for each type we support 
     template<typename Type = Shape> 
     virtual void modify(Type* pShape /*,glm::vec3... params*/); 

     // Overloaded Types: - Should Be Defined & Overridden By the Derived Class 
     virtual void modify<Triangle>(Triangle* pTriangle, glm::vec3, glm::vec3, glm::vec3, glm::vec4 = glm::vec4()) { /* ... */ } 
     virtual void modify<Circle>(Cirlce* pCircle, float radius, glm::vec4 color = glm::vec4()) { /* ... */} 

}; 

Ensuite, un hérité la classe ressemblerait à quelque chose comme:

class Triangle : public Shape { 
public: 
    // Could Be An Option To Where This is a base class as well to specific types of triangles: 
    enum TriangleType { 
     Acute = 0, 
     Right, 
     Equilateral, 
     Obtuse 
    } // then each of these would have properties specific to each type 
private: 
    glm::vec3[3] vertices_; 

public: 
    // Default Constructor 
    Triangle() : Shape(TRIANGLE) {} // Sets The Shape Type But Has No Vertices Or Area; just default construction 
    // Vertices But No Color 
    Triangle(glm::vec3 A, glm::vec3 B, glm::vec3 C) : Shape(TRIANGLE) { 
     vertices_[0] = A; 
     vertices_[1] = B; 
     vettices_[2] = C; 

     // Call These To Have These Values 
     calculatePerimeter(); 
     calculateArea();    
    } 
    // Vertices & Color 
    Triangle(glm::vec3 A, glm::vec3 B, glm::vec3 C, glm::vec4 color) : Shape(TRIANGLE) { 
     vertices_[0] = A; 
     vertices_[1] = B; 
     vertices_[2] = C; 

     calculatePerimeter(); 
     calculateArea(); 
    } 

    // No Need To Do The Set & Get Colors - Base Class Does that for you. 

    // Methods that this shape must implement 
    virtual double calculateArea() override { 
     // Calculations For Getting Area of A Triangle 
     area_ = /* calculation */; 
    }; 
    virtual double calculatePerimeter() override { 
     // Calculations For Getting Perimeter of A Triangle 
     perimeter_ = /* calculation */; 
    }; 

    void modify<Triangle>(Triangle* pTriangle, glm::vec3, glm::vec3, glm::vec3, glm::vec4 = glm::vec4()) override { /* ... */ } 

}; 

Maintenant comme pour afficher l'information; Personnellement, je ne mettrais pas en œuvre dans ces classes. Il suffit d'utiliser votre niveau std::cout ou std::ofstream etc. pour imprimer les valeurs à l'écran ou simplement acheter fichier en utilisant le getters comme celui-ci:

#include <iostream> 
#include "Triangle.h" 

int main() { 
    Triangle t1(glm::vec3(0.0f, 1.0f, -1.3f), // Vertex A 
       glm::vec3(3.2f, 5.5f, -8.9f), //  B 
       glm::vec3(-4.5f, 7.6f, 8.2f), //  C 
       glm::vec4(0.8f, 0.9f, 0.23f, 1.0f)); // Color 

    std::cout << "Perimeter is " << t1.getPerimeter() << std::endl; 
    std::cout << "Area is " << t1.getArea() << std::endl; 

    return 0; 
}