320 likes | 406 Views
An Example Program. Representing a Mesh Solution One: Arrays Abstraction and Encapsulation Solution Two: Introducing Classes Solution Three: Information Hiding. Representing a Mesh. Reading input 8 5 2.0 6.0 2.0 2.0 4.0 4.0 4.1 1.0 6.0 5.0
E N D
An Example Program • Representing a Mesh • Solution One: Arrays • Abstraction and Encapsulation • Solution Two: Introducing Classes • Solution Three: Information Hiding
Representing a Mesh • Reading input • 8 5 2.0 6.0 2.0 2.0 4.0 4.0 4.1 1.0 6.0 5.0 • 6.0 2.0 8.0 3.0 9.0 1.0 3 1 2 0 3 3 2 1 • 3 5 2 3 4 6 4 2 5 3 7 6 5 • 1. number of nodes • 2. number of elements • 3. node coordinates • 4. elements: • - number of nodes • - node numbers (counterclockwise) • Checking mesh quality: • are there elements with a large angle
(0) (4) (2) 0 3 (6) 1 2 (1) 4 (5) (3) (7) 6 5 4 3 2 1 1 2 3 4 5 6 7 8 9 852.0 6.0 2.0 2.0 4.0 4.0 4.1 1.0 6.0 5.0 6.0 2.0 8.0 3.0 9.0 1.03 1 2 0 3 3 2 1 3 5 2 3 4 6 4 2 5 3 7 6 5
Requirements • The representation of nodes and elments must be compact. • It must be possible to find quickly the coordinates of a node and the nodes of an element.
Review Criteria • Is the code readable and understandable? Is the overall structure apperant without understanding the details? • Suppose the representation must be changed to admit some new use. How much existing code that uses the representation would need to be changed?
0: 2.0 6.0 1: 2.0 2.0 2: 4.0 4.0 3: 4.0 1.0 4: 6.0 5.0 5: 6.0 2.0 6: 8.0 3.0 7: 9.0 1.0 0: 3 1 2 0 1: 3 3 2 1 2: 3 5 2 3 3: 4 6 4 2 5 4: 3 7 6 5 Solution One: Arrays SimpleArray<Point> nodeTable(numNodes); typedef SimpleArray<int> Element; SimpleArray<Element> elementTable(numElements);
typedef SimpleArray<int> Element; • int main() { • // Read sizes and create tables • int numNodes; • int numElements; • cin >> numNodes >> numElements; • SimpleArray<Point> nodeTable(numNodes); • SimpleArray<Element> elementTable(numElements); • // Read node data • for (int nodeNum = 0; nodeNum < numNodes; nodeNum++) { • cin >> nodeTable[nodeNum]; • }
// Read element data • for (int eltNum = 0; eltNum < numElements; eltNum++) { • // e is an alias for element # eltNum • Element& e = elementTable[eltNum]; • int elementSize; • cin >> elementSize; • e.setSize(elementSize); • for (int i = 0; i < elementSize; i++) cin >> e[i]; • }
// Read maximum angle threshold and check elements • Number angle_threshold; • cin >> angle_threshold; • bool anglesOK = true; • for (eltNum = 0; eltNum < numElements; eltNum++) { • // e is an alias for element # eltNum • Element& e = elementTable[eltNum]; • if (maxAngle(e, nodeTable) > angle_threshold) { • cout << "Element [ "; • for (int i = 0; i < e.numElts(); i++) { • cout << nodeTable[e[i]] << " "; • } • cout << "] has a large angle." << endl; • anglesOK = false; • } • } • return anglesOK ? 0 : 1; • }
Number maxAngle(const Element& e, const SimpleArray<Point>& nodeTable) { • Number maxang = 0.0; • int numNodes = e.numElts(); • for (int i = 0; i < numNodes; i++) { • int ccwNodeNum = (i + 1) % numNodes; • int cwNodeNum = (i + numNodes - 1) % numNodes; • Number angle = nodeTable[e[i]].angle( • nodeTable[e[cwNodeNum]], • nodeTable[e[ccwNodeNum]] • ); • if ( angle > maxang ) maxang = angle; • } • return maxang; • }
Review: Solution One • Two tasks: 1. building the node and element table from input build the representation 2. checking each element against an angle threshold using the representation • In a real program a small amount of code would build the representation and more code would use it. • Suppose providing new features required changing the representation: 1. We expect to change the code that builds the rep. 2. Do we have to change the code that uses it.
The angle-checking code depends on the following assumptions: 1. There is only one mesh. 2. There is an element table that is indexed by number. 3. There is a node table that is indexed by node number. 4. Nodes of an n-node element are indexed with respect to individual elements such that the neighbors of the node indexed i are indexed i+1 mod n (counterclockwise) and i-1 mod n (clockwise).
Abstraction and Encapsulation Parnas's information hiding principle: Hide from an abstraction's clients design decisions that are likely to change. Keep data structures secret. If a client depend on an object's data structures, changing the data structure will cost at least the time to understand the client's code, and maybe more.
Solution Two: Classes • typedef Point Node; • class Element { • public: • int operator[](int i) const; • int numNodes() const; • friend istream& operator>>(istream&, Element&); • private: • SimpleArray<int> node_numbers; • }; Give names to abstractions.
int Element::operator[](int i) const {return node_numbers[i];} • int Element::numNodes() const { return node_numbers.numElts();} • istream& operator>>(istream& instream, Element& e) { • int n; • instream >> n; • e.node_numbers.setSize(n); • for (int i = 0; i < n; i++) instream >> e.node_numbers[i]; • return instream; • }
class Mesh { • public: • bool checkElementAngles(Number angle_threshold) const; • Number maxAngle(const Element& e) const; • int numNodes() const; • int numElements() const; • friend istream& operator>>(istream&, Mesh&); • private: • SimpleArray<Node> node_table; • SimpleArray<Element> element_table; • }; • int Mesh::numNodes() const { return node_table.numElts(); } • int Mesh::numElements() const { return element_table.numElts(); }
Number Mesh::maxAngle(const Element& e) const { • Number maxang = 0.0; • int numNodes = e.numNodes(); • for (int i = 0; i < numNodes; i++) { • int ccwNodeNum = (i + 1) % numNodes; • int cwNodeNum = (i + numNodes - 1) % numNodes; • Number angle = node_table[e[i]].angle( • node_table[e[cwNodeNum]], • node_table[e[ccwNodeNum]] • ); • if ( angle > maxang ) maxang = angle; • } • return maxang; • }
bool Mesh::checkElementAngles(Number angle_threshold) const { • bool anglesOK = true; • for (int eltNum = 0; eltNum < numElements(); eltNum++) { • // e is an alias for element # eltNum • const Element& e = element_table[eltNum]; • if (maxAngle(e) > angle_threshold) { • cout << "Element [ "; • for (int i = 0; i < e.numNodes(); i++) { • cout << node_table[e[i]] << " "; • } • cout << "] has a large angle." << endl; • anglesOK = false; • } • } • return anglesOK; • }
istream& operator>>(istream& instream, Mesh& m) { • int nNodes; • int nElements; • instream >> nNodes >> nElements; • m.node_table.setSize(nNodes); • m.element_table.setSize(nElements); • for (int nodeNum = 0; nodeNum < nNodes; nodeNum++) { • instream >> m.node_table[nodeNum]; • } • for (int elementNum = 0; elementNum < nElements; elementNum++) { • instream >> m.element_table[elementNum]; • } • return instream; • }
Element int Element[i] int numNodes() istream >> Element Mesh bool checkAngles() int numNodes() int numElements() istream >> Mesh * * Node
int main() { • Mesh m; • cin >> m; • Number angle_threshold; • cin >> angle_threshold; • bool anglesOK = m.checkElementAngles(angle_threshold); • return anglesOK ? 0 : 1; • }
Review: Solution Two • Abstractions: Node, Element, Mesh • Benefit = clarity of the main() function. • Problems: ...
Problems • class Element { • public: • int operator[](int i) const; • int numNodes() const; • friend istream& operator>>(istream&, Element&); • private: • SimpleArray<int> node_numbers; • }; 1. maxAngle() should be in Element 2. the subscript operator [] does not fit the abstraction Element (it takes an integer and returns an integer only relevant to the implementation of meshes with node and element tables).
Unless arraylike behavior is part of the abstraction that a class implements, avoid providing a member function that takes or returns an integer index argument.
Solution Three: Information Hiding • class Mesh { • public: • friend ElementsOfMesh; // Iterator over elements of a mesh • friend NodesOfMesh; // Iterator over nodes of a mesh • friend istream& operator>>(istream&, Mesh&); • friend NodeReader; • bool checkElementAngles(Number angle_threshold) const; • private: • SimpleArray<Node> node_table; • SimpleArray<Element> element_table; • };
class Element { • public: • friend NodesOfElement; // Iterator over nodes of an element • friend void operator>>(NodeReader& reader, Element& e); • friend ostream& operator<<(ostream& os, const Element& e); • Number maxAngle() const; • private: • SimpleArray<Node*> node_ptrs; • }; • class NodeReader { • public: • NodeReader(Mesh& m, istream& instream); • int getSize(); • Node* getNode(); • private: • Mesh& mesh; • istream& in; • };
istream& operator>>(istream& instream, Mesh& m) { • // Set sizes of node and element tables • int nNodes; • int nElements; • instream >> nNodes >> nElements; • m.node_table.setSize(nNodes); • m.element_table.setSize(nElements); • // Read nodes • for (int nodeNum = 0; nodeNum < nNodes; nodeNum++) { • instream >> m.node_table[nodeNum]; • } • // Read elements • NodeReader reader(m, instream); • for (int elementNum = 0; elementNum < nElements;elementNum++) { • reader >> m.element_table[elementNum]; • } • return instream; • }
NodeReader::NodeReader(Mesh& m, istream& instream) : • mesh(m), in(instream) {} • int NodeReader::getSize() { • int size; • in >> size; • return size; • } • Node* NodeReader::getNode() { • int nodeNum; • in >> nodeNum; • return &mesh.node_table[nodeNum]; • }
class ElementsOfMesh { • public: • ElementsOfMesh(const Mesh& m) : • element_table(m.element_table), • cur(m.element_table.numElts()-1) { } • bool more() const { return cur >= 0; } • void advance() { --cur; } • const Element& current() const { return element_table[cur]; } • private: • const SimpleArray<Element>& element_table; • int cur; • }; • Boolean Mesh::checkElementAngles(Number angle_threshold) const { • bool anglesOK = true; • for (ElementsOfMeshelts(*this); elts.more(); elts.advance()) { • if (elts.current().maxAngle() > angle_threshold) { • cout << "Element " << elts.current() • << " has a large angle." << endl; • anglesOK = false; • } • } • return anglesOK; • }
class NodesOfElement { • public: • NodesOfElement(const Element& e) : • node_ptrs(e.node_ptrs), cur(e.node_ptrs.numElts()-1) {} • // Iteration • Boolean more() const { return cur >= 0; } • void advance() { --cur; } • Node& current() const { return *node_ptrs[cur]; } • // Neighbors of current iterate • Node& ccwNeighbor() const { • return *node_ptrs[(cur + 1) % node_ptrs.numElts()]; • } • Node& cwNeighbor() const { • int numNodes = node_ptrs.numElts(); • return *node_ptrs[(cur + numNodes - 1) % numNodes]; • } • private: • const SimpleArray<Node*>& node_ptrs; • int cur; • };
Number Element::maxAngle() const { • Number maxang = 0.0; • for (NodesOfElement nodes(*this); nodes.more(); nodes.advance()) { • Number angle = nodes.current().angle( • nodes.cwNeighbor(), • nodes.ccwNeighbor() • ); • if ( angle > maxang ) maxang = angle; • } • return maxang; • }
ElementsOfMesh bool more() void advance() Element current() NodesOfMesh bool more() void advance() Node current() NodeReader int getSize() Node* getNode() NodesOfElement bool more() void advance() Element current() Node ccwNeighbor() Node cwNeighbor() istream Node * Mesh bool checkAngles() * * Element int maxAngle()