Classes Working Together
A single class packages one concept. Real programs package many. This page builds three classes that work together: one from Concept #6 (slightly extended), one that inherits from it, and one that uses both — each exposing a different OOP relationship.
Composition is when a class holds an instance of another class as an instance variable. Quadratic has a vertex field of type OrderedPair. The quadratic does not become an ordered pair; it contains one. Composition models “has-a” relationships.
Inheritance is when a class extends another class, acquiring all of its fields and methods automatically. ComplexOrderedPair extends OrderedPair: every getter, every constructor path, every utility method of OrderedPair is available in ComplexOrderedPair for free. The child then overrides toString() to display a + bi instead of (a, b), and adds two new methods — conjugate() and modulus() — that have no meaning in the parent but are essential for complex numbers. Inheritance models “is-a” relationships.
When Quadratic calculates roots and the discriminant is negative, it creates ComplexOrderedPair objects to represent the complex roots. That single line connects all three classes: the inheritance hierarchy provides the complex number type; the composition provides the host object that requests it.
The PCNICOTGSU mnemonic is the teacher’s shorthand for the ten anatomy elements every well-formed class should have. It is embedded as a comment in the Java and JavaScript versions so you can see each element in place. The Quadratic class is designed to be a reference example: every letter of the mnemonic has at least one corresponding line of code.
There’s more to this story. The Java version of Quadratic was later upgraded twice.
Upgrade 1 stores roots as OrderedPair objects, adds a shared DecimalFormat, and fixes double comparisons with EPSILON.
Upgrade 2 adds final constants, an explicit extends Object, axis of symmetry, a memoryAddress ivar via super.toString(), and a live aliasing trap demonstration.
See:
Upgrade 1 Showcase,
Upgrade 1 Analysis,
Upgrade 2 Showcase,
Upgrade 2 Analysis.
Inheritance chain & composition map
PCNICOTGSU — 10 anatomy elements
Every code panel below is annotated with these labels. Find each letter in the Quadratic class source.
Calling the Classes — Driver Programs
These files exercise all three constructors, getRootsDescription() across all three discriminant cases, and setter side effects. Run them after loading the four class files.
// Paste OrderedPair.js, ComplexOrderedPair.js,
// Quadratic.js, then this file — DevTools Console.
console.log("=== Quadratic Runner ===\n");
console.log("--- Default: f(x) = x\u00B2 ---");
var q1 = new Quadratic();
console.log(q1.toString());
console.log("f(3) = " + q1.f(3));
console.log("Roots: " + q1.getRootsDescription());
console.log("\n--- Two real roots: x\u00B2 - 5x + 6 ---");
var q2 = new Quadratic(1, -5, 6);
console.log(q2.toString());
console.log("Roots: " + q2.getRootsDescription());
console.log("\n--- One repeated root: x\u00B2 - 6x + 9 ---");
var q3 = new Quadratic(1, -6, 9);
console.log(q3.toString());
console.log("Roots: " + q3.getRootsDescription());
console.log("\n--- Complex roots: x\u00B2 + 2x + 5 ---");
var q4 = new Quadratic(1, 2, 5);
console.log(q4.toString());
console.log("Roots: " + q4.getRootsDescription());
console.log("\n--- Setter side effects ---");
var q5 = new Quadratic(1, 0, -4);
console.log("Before: " + q5.toString());
q5.setB(-4);
console.log("After setB(-4): " + q5.toString());
console.log("\n--- Copy constructor ---");
var q6 = new Quadratic(q2);
console.log("Copy of q2: " + q6.toString());
console.log("\n--- Thanks for using our program! ---");
// JDoodle multi-file (Java): 4 files required —
// File 1: OrderedPair.java
// File 2: ComplexOrderedPair.java
// File 3: Quadratic.java
// File 4: QuadraticDriver.java ← set as main file
public class QuadraticDriver {
public static void main(String[] args){
System.out.println("=== Quadratic Driver ===\n");
System.out.println("--- Default: f(x) = x\u00B2 ---");
Quadratic q1 = new Quadratic();
System.out.println(q1);
System.out.println("f(3) = " + q1.f(3));
System.out.println("Roots: " + q1.getRootsDescription());
System.out.println(
"\n--- Two real roots: x\u00B2 - 5x + 6 ---");
Quadratic q2 = new Quadratic(1, -5, 6);
System.out.println(q2);
System.out.println("Roots: " + q2.getRootsDescription());
System.out.println(
"\n--- One repeated root: x\u00B2 - 6x + 9 ---");
Quadratic q3 = new Quadratic(1, -6, 9);
System.out.println(q3);
System.out.println("Roots: " + q3.getRootsDescription());
System.out.println(
"\n--- Complex roots: x\u00B2 + 2x + 5 ---");
Quadratic q4 = new Quadratic(1, 2, 5);
System.out.println(q4);
System.out.println("Roots: " + q4.getRootsDescription());
System.out.println("\n--- Setter side effects ---");
Quadratic q5 = new Quadratic(1, 0, -4);
System.out.println("Before: " + q5);
q5.setB(-4);
System.out.println("After setB(-4): " + q5);
System.out.println("\n--- Copy constructor ---");
Quadratic q6 = new Quadratic(q2);
System.out.println("Copy of q2: " + q6);
System.out.println(
"\n--- Thanks for using our program! ---");
}//end main
}//end class QuadraticDriver
# Add OrderedPair.py, ComplexOrderedPair.py,
# and Quadratic.py as extra files in OnlineGDB.
from Quadratic import Quadratic
def main():
print("=== Quadratic Main ===\n")
print("--- Default: f(x) = x\u00B2 ---")
q1 = Quadratic()
print(q1)
print(f"f(3) = {q1.f(3)}")
print("Roots:", q1.get_roots_description())
print("\n--- Two real roots: x\u00B2 - 5x + 6 ---")
q2 = Quadratic(1, -5, 6)
print(q2)
print("Roots:", q2.get_roots_description())
print("\n--- One repeated root: x\u00B2 - 6x + 9 ---")
q3 = Quadratic(1, -6, 9)
print(q3)
print("Roots:", q3.get_roots_description())
print("\n--- Complex roots: x\u00B2 + 2x + 5 ---")
q4 = Quadratic(1, 2, 5)
print(q4)
print("Roots:", q4.get_roots_description())
print("\n--- Setter side effects ---")
q5 = Quadratic(1, 0, -4)
print("Before:", q5)
q5.set_b(-4)
print("After set_b(-4):", q5)
print("\n--- Copy constructor ---")
q6 = Quadratic.from_quadratic(q2)
print("Copy of q2:", q6)
print("\n--- Thanks for using our program! ---")
if __name__ == "__main__":
main()
OrderedPair — the Foundation
This is Concept #6’s OrderedPair with two additions: getX() and getY(). These getters are required so ComplexOrderedPair’s copy constructor can read the parent’s private coordinates. For JDoodle, this is File 1 of 4 — paste it first before the other three files.
// getX() and getY() added for subclass access
class OrderedPair {
constructor(xOrOrig, y) {
if (xOrOrig instanceof OrderedPair) {
console.log("...OrderedPair copy constructor...");
this._x = xOrOrig._x;
this._y = xOrOrig._y;
this._absVal = this._computeAbsVal();
this._label = xOrOrig._label + "_copy";
} else if (xOrOrig !== undefined
&& y !== undefined) {
console.log("...OrderedPair two-parameter constructor...");
this._x = xOrOrig;
this._y = y;
this._absVal = this._computeAbsVal();
this._label = "P";
} else {
console.log("...OrderedPair default constructor...");
this._x = 0;
this._y = 0;
this._absVal = this._computeAbsVal();
this._label = "O";
}
}
toString() {
return this._label
+ "(" + this._x + ", " + this._y + ")";
}
getX() { return this._x; }
getY() { return this._y; }
getAbsVal() { return this._absVal; }
getLabel() { return this._label; }
setLabel(lbl) {
console.log("...setLabel...");
this._label = lbl;
}
_computeAbsVal() {
console.log("...computeAbsVal...");
return Math.sqrt(
Math.pow(this._x, 2) + Math.pow(this._y, 2));
}
transpose() {
console.log("...transpose...");
var temp = this._y;
this._y = this._x;
this._x = temp;
this._absVal = this._computeAbsVal();
}
}//end class OrderedPair
// Concept #6 + getX() / getY() for subclass access
public class OrderedPair {
private double x;
private double y;
private double absVal;
private String label;
public OrderedPair(){
System.out.println("...OrderedPair default constructor...");
x = 0; y = 0;
absVal = computeAbsVal();
label = "O";
}
public OrderedPair(double x, double y){
System.out.println(
"...OrderedPair two-parameter constructor...");
this.x = x; this.y = y;
absVal = computeAbsVal();
label = "P";
}
public OrderedPair(OrderedPair orig){
System.out.println("...OrderedPair copy constructor...");
x = orig.x; y = orig.y;
absVal = computeAbsVal();
label = orig.label + "_copy";
}
public String toString(){
return label + "(" + x + ", " + y + ")";
}
public double getX() { return x; }
public double getY() { return y; }
public double getAbsVal() { return absVal; }
public String getLabel() { return label; }
public void setLabel(String lbl){
System.out.println("...setLabel...");
label = lbl;
}
public double computeAbsVal(){
System.out.println("...computeAbsVal...");
return Math.sqrt(
Math.pow(x, 2) + Math.pow(y, 2));
}
public void transpose(){
System.out.println("...transpose...");
double temp = y; y = x; x = temp;
absVal = computeAbsVal();
}
}//end class OrderedPair
import math
class OrderedPair:
"""Concept #6 + get_x() / get_y() for subclass access."""
def __init__(self, x=0.0, y=0.0):
print("...OrderedPair constructor...")
self._x = x
self._y = y
self._abs_val = self._compute_abs_val()
self._label = "O" \
if (x == 0.0 and y == 0.0) else "P"
@classmethod
def from_ordered_pair(cls, orig):
"""Copy constructor."""
print("...OrderedPair copy constructor (classmethod)...")
instance = cls(orig._x, orig._y)
instance._label = orig._label + "_copy"
return instance
def __str__(self):
return (self._label
+ "(" + str(self._x)
+ ", " + str(self._y) + ")")
def get_x(self): return self._x
def get_y(self): return self._y
def get_abs_val(self): return self._abs_val
def get_label(self): return self._label
def set_label(self, lbl):
print("...set_label...")
self._label = lbl
def _compute_abs_val(self):
print("...compute_abs_val...")
return math.sqrt(self._x ** 2 + self._y ** 2)
def transpose(self):
print("...transpose...")
self._x, self._y = self._y, self._x
self._abs_val = self._compute_abs_val()
#end class OrderedPair
ComplexOrderedPair extends OrderedPair
Inherits everything from OrderedPair. Overrides toString() to show a + bi. Adds conjugate() and modulus(). No new fields needed — x is the real part, y is the imaginary part.
// x = real part, y = imaginary part of a + bi
class ComplexOrderedPair extends OrderedPair {
constructor(xOrOrig, y) {
if (xOrOrig instanceof ComplexOrderedPair) {
// copy path
console.log("...ComplexOrderedPair copy constructor...");
super(xOrOrig.getX(), xOrOrig.getY());
this.setLabel(xOrOrig.getLabel());
} else if (xOrOrig !== undefined && y !== undefined) {
// real + imaginary
console.log("...ComplexOrderedPair two-parameter constructor...");
super(xOrOrig, y);
this.setLabel("z");
} else {
// default: 0 + 0i
console.log("...ComplexOrderedPair default constructor...");
super();
this.setLabel("z");
}
}
// Override toString — a+bi notation
toString() {
var r = this.getX();
var i = this.getY();
if (i === 0) return "" + r;
if (r === 0) return i + "i";
if (i > 0) return r + " + " + i + "i";
return r + " - " + Math.abs(i) + "i";
}
// New methods added by the subclass
conjugate() {
return new ComplexOrderedPair(this.getX(), -this.getY());
}
modulus() { return this.getAbsVal(); }
}//end class ComplexOrderedPair
// extends = inherits all OrderedPair fields and methods
public class ComplexOrderedPair extends OrderedPair {
// default constructor: 0 + 0i
public ComplexOrderedPair(){
super(); // calls OrderedPair()
setLabel("z");
}
// two-parameter: real + imaginary
public ComplexOrderedPair(double real, double imaginary){
super(real, imaginary); // calls OrderedPair(x,y)
setLabel("z");
}
// copy constructor — must use getters (orig.x is private!)
public ComplexOrderedPair(ComplexOrderedPair orig){
super(orig.getX(), orig.getY());
setLabel(orig.getLabel());
}
// @Override toString — show a+bi instead of (real, imag)
@Override
public String toString(){
double r = getX(); // inherited getter
double i = getY(); // inherited getter
if (i == 0) return "" + r;
if (r == 0) return i + "i";
if (i > 0) return r + " + " + i + "i";
return r + " - " + Math.abs(i) + "i";
}
// New behavior added by the subclass
public ComplexOrderedPair conjugate(){
return new ComplexOrderedPair(getX(), -getY());
}
public double modulus(){ return getAbsVal(); }
}//end class ComplexOrderedPair
from OrderedPair import OrderedPair
import math
# (ClassName) = inherits from OrderedPair
class ComplexOrderedPair(OrderedPair):
"""x = real part, y = imaginary part."""
def __init__(self, real=0.0, imaginary=0.0):
super().__init__(real, imaginary) # calls OrderedPair.__init__
self._label = "z"
print("...ComplexOrderedPair constructor...")
@classmethod
def from_complex(cls, orig):
"""Copy constructor."""
instance = cls(orig.get_x(), orig.get_y())
instance._label = orig.get_label()
return instance
# Override __str__ — a+bi notation
def __str__(self):
r = self.get_x()
i = self.get_y()
if i == 0: return str(r)
if r == 0: return str(i) + "i"
if i > 0: return str(r) + " + " + str(i) + "i"
return str(r) + " - " + str(abs(i)) + "i"
# New methods added by the subclass
def conjugate(self):
return ComplexOrderedPair(self.get_x(), -self.get_y())
def modulus(self):
return self.get_abs_val() # inherited
#end class ComplexOrderedPair
Quadratic — the Full PCNICOTGSU Class
Every PCNICOTGSU element is present and annotated. Note how the setters call computeDiscriminant() and computeVertex() as side effects — a direct application of the DWR lesson from Concept #7.
// Quadratic: f(x) = ax\u00B2 + bx + c
// PCNICOTGSU anatomy annotated throughout
class Quadratic { // P C N
constructor(aOrOrig, b, c) { // C
if (aOrOrig instanceof Quadratic) {
this._a = aOrOrig._a; this._b = aOrOrig._b;
this._c = aOrOrig._c;
} else if (aOrOrig !== undefined) {
if (aOrOrig === 0) throw new Error("a\u22600");
this._a = aOrOrig; this._b = b; this._c = c;
} else {
this._a = 1; this._b = 0; this._c = 0;
}
this._computeDiscriminant();
this._computeVertex();
}
toString() { // O/T
return "f(x) = " + this._a + "x\u00B2 + "
+ this._b + "x + " + this._c
+ " | vertex: " + this._vertex.toString()
+ " | disc: " + this._discriminant;
}
// Getters G
getA() { return this._a; }
getB() { return this._b; }
getC() { return this._c; }
getDiscriminant() { return this._discriminant; }
getVertex() { return this._vertex; }
// Setters — validate + side effects S
setA(a) {
if (a === 0) throw new Error("a\u22600");
this._a = a;
this._computeDiscriminant(); this._computeVertex();
}
setB(b) { this._b = b; this._computeDiscriminant(); this._computeVertex(); }
setC(c) { this._c = c; this._computeDiscriminant(); this._computeVertex(); }
// Utility methods U
f(x) { return this._a*x*x + this._b*x + this._c; }
_computeDiscriminant() {
this._discriminant = this._b*this._b - 4*this._a*this._c;
}
_computeVertex() {
var h = -this._b / (2 * this._a);
this._vertex = new OrderedPair(h, this.f(h));
this._vertex.setLabel("V");
}
getRootsDescription() {
var d = this._discriminant;
if (d > 0) {
var r1 = (-this._b + Math.sqrt(d)) / (2*this._a);
var r2 = (-this._b - Math.sqrt(d)) / (2*this._a);
return "Two real: x=" + r1 + " and x=" + r2;
} else if (d === 0) {
return "One root: x=" + (-this._b/(2*this._a));
} else {
var rp = -this._b/(2*this._a);
var ip = Math.sqrt(-d)/(2*this._a);
return "Complex: " +
new ComplexOrderedPair(rp, ip).toString() +
" and " +
new ComplexOrderedPair(rp, -ip).toString();
}
}
}//end class Quadratic
//(P)erhaps (C)lown (N)onsense (I)s (C)onstructive
//(O)nly (T)oward (G)etting (S)ettlers (U)nderwear
public class Quadratic { // P C N
// Ivars ─────────────────────────────────── I
private double a, b, c;
private double discriminant;
private OrderedPair vertex; // another class as ivar!
// Constructors ───────────────────────────── C
public Quadratic(){
a=1; b=0; c=0;
computeDiscriminant(); computeVertex();
}
public Quadratic(double a, double b, double c){
if (a==0) throw new
IllegalArgumentException("a cannot be zero");
this.a=a; this.b=b; this.c=c;
computeDiscriminant(); computeVertex();
}
public Quadratic(Quadratic orig){
a=orig.a; b=orig.b; c=orig.c;
computeDiscriminant(); computeVertex();
}
// toString ───────────────────────────────── O/T
@Override public String toString(){
return "f(x)="+a+"x\u00B2+"+b+"x+"+c
+" vertex:"+vertex+" disc:"+discriminant;
}
// Getters ────────────────────────────────── G
public double getA() { return a; }
public double getDiscriminant(){ return discriminant; }
public OrderedPair getVertex() { return vertex; }
// Setters — validate + side effects ──────── S
public void setA(double a){
if (a==0) throw new
IllegalArgumentException("a cannot be zero");
this.a=a;
computeDiscriminant(); // side effect
computeVertex(); // side effect
}
public void setB(double b){
this.b=b; computeDiscriminant(); computeVertex();
}
public void setC(double c){
this.c=c; computeDiscriminant(); computeVertex();
}
// Utility methods ────────────────────────── U
public double f(double x){ return a*x*x + b*x + c; }
private void computeDiscriminant(){
discriminant = b*b - 4*a*c;
}
private void computeVertex(){
double h = -b/(2*a);
vertex = new OrderedPair(h, f(h));
vertex.setLabel("V");
}
public String getRootsDescription(){
if (discriminant > 0){
double r1=(-b+Math.sqrt(discriminant))/(2*a);
double r2=(-b-Math.sqrt(discriminant))/(2*a);
return "Two real: x="+r1+" and x="+r2;
} else if (discriminant == 0){
return "One root: x="+(-b/(2*a));
} else {
// disc < 0: ComplexOrderedPair enters the chat!
double rp=-b/(2*a);
double ip=Math.sqrt(-discriminant)/(2*a);
return "Complex: "
+new ComplexOrderedPair(rp,ip)
+" and "+new ComplexOrderedPair(rp,-ip);
}
}
}//end class Quadratic
from OrderedPair import OrderedPair
from ComplexOrderedPair import ComplexOrderedPair
import math
class Quadratic:
"""f(x) = ax\u00B2 + bx + c (a \u2260 0)."""
def __init__(self, a=1.0, b=0.0, c=0.0): # C
if a == 0: raise ValueError("a \u22600")
self._a = a; self._b = b; self._c = c
self._discriminant = 0.0
self._vertex = None
self._compute_discriminant()
self._compute_vertex()
@classmethod
def from_quadratic(cls, orig): # C copy
return cls(orig._a, orig._b, orig._c)
def __str__(self): # O/T
return (f"f(x)={self._a}x\u00B2+{self._b}x+{self._c}"
f" vertex:{self._vertex}"
f" disc:{self._discriminant}")
# Getters G
def get_a(self): return self._a
def get_discriminant(self): return self._discriminant
def get_vertex(self): return self._vertex
# Setters — validate + side effects S
def set_a(self, a):
if a == 0: raise ValueError("a \u22600")
self._a = a
self._compute_discriminant() # side effect
self._compute_vertex() # side effect
def set_b(self, b):
self._b = b
self._compute_discriminant()
self._compute_vertex()
def set_c(self, c):
self._c = c
self._compute_discriminant()
self._compute_vertex()
# Utility methods U
def f(self, x):
return self._a*x**2 + self._b*x + self._c
def _compute_discriminant(self):
self._discriminant = self._b**2 - 4*self._a*self._c
def _compute_vertex(self):
h = -self._b / (2 * self._a)
self._vertex = OrderedPair(h, self.f(h))
self._vertex.set_label("V")
def get_roots_description(self):
d = self._discriminant
if d > 0:
r1 = (-self._b + math.sqrt(d)) / (2*self._a)
r2 = (-self._b - math.sqrt(d)) / (2*self._a)
return f"Two real: x={r1} and x={r2}"
elif d == 0:
return f"One root: x={-self._b/(2*self._a)}"
else:
rp = -self._b / (2*self._a)
ip = math.sqrt(-d) / (2*self._a)
z1 = ComplexOrderedPair(rp, ip)
z2 = ComplexOrderedPair(rp, -ip)
return f"Complex: {z1} and {z2}"
#end class Quadratic
Why Does Each Version Look Different?
“What does each version reveal about the language’s personality?”
One constructor, all three paths — same as Concept #6. JavaScript allows exactly one constructor per class. The inheritance pattern via extends and super() is identical in syntax to Java, but the enforcement is not: nothing in JavaScript prevents you from bypassing the gate. _computeVertex is private by convention, not enforcement.
extends in ES6 is real prototype-based inheritance. When ComplexOrderedPair extends OrderedPair, JavaScript builds a prototype chain. this.getX() in ComplexOrderedPair walks up that chain to OrderedPair.prototype.getX. The method lookup is dynamic: a subclass method shadows a parent method with the same name, which is exactly how toString() override works.
Constructor overloading + @Override guarantee. Java’s three separate constructors are distinct, compiler-resolved signatures. The @Override annotation on toString() is not decoration — the compiler verifies that a method with this exact signature actually exists in the parent. If you misspell it or change the return type, the annotation causes a compile error rather than a silent shadow. That is a safety net JavaScript cannot offer.
Copy constructor accesses the parent’s private fields directly. In Quadratic’s copy constructor, this.a = orig.a compiles because both objects are the same class — Java allows private access within the same class regardless of which instance. In ComplexOrderedPair’s copy constructor, orig.x would fail because x is private in OrderedPair, a different class. Getters are required. This is the distinction that forces the getX() and getY() additions.
Single-argument super().__init__() — no class name, no self, no repeated ceremony. Python 3 resolves the Method Resolution Order (MRO) automatically. In contrast to Java’s explicit super(real, imaginary), Python’s cooperative super().__init__(real, imaginary) participates in the full inheritance chain, which matters in multiple-inheritance scenarios Python supports and Java does not.
from_quadratic is a @classmethod, not a constructor overload. Python has one __init__. The copy-constructor pattern requires a factory classmethod. This is the same pattern seen in Concept #6. It is more explicit than Java (you type Quadratic.from_quadratic(q2), not just new Quadratic(q2)) but reveals Python’s philosophy: named factory methods communicate intent clearly; overloaded constructors rely on type matching.