Define an abstract Shape class with an area method, and a concrete Circle subclass.

← Classes and OOP · Ref: Q1180

Abstract classes use 'as abstract' and concrete children override:

  Shape as abstract
    name as String: String()
    area() as abstract
      <- rtn as Float?
    default operator ?
    default operator $
  Circle extends Shape
    override area()
      <- rtn as Float: ...
    override operator ? as pure
      ...
    default operator $

WHERE 'default operator' CAN GO
Shape takes 'default operator ?' and 'default operator $', so Circle can default '$' too: its generated '$' calls Shape's and adds its own field - 'Circle(Shape(name=circle), radius=5.0)'. Circle must NOT add a bare 'default operator': that asks for a generated <=> built on the super's <=>, and Shape has none, so it is E07190 (requires operator '<=>(Shape)'). Circle defines just the operators it needs (? and $).
An abstract class may itself take a full 'default operator', and a child's 'default operator' then compiles. But sameness (== and <=>) is defined once per hierarchy, at Shape, so the child's 'default operator' does not give Circle its own == or <=> - and two Circles with different radii compare EQUAL under Shape's sameness (Circle(2.0) == Circle(3.0) is true). That is a poor fit for a Shape hierarchy, which is why this example does not use it.

AN ABSTRACT '$' ONLY WITHOUT FIELDS

Shape has a field ('name'), so its '$' cannot be abstract: 'operator $ as pure abstract' here is E07185. Below an abstract '$' a sub-type's generated '$' covers only the sub-type's own fields, so Shape's 'name' would silently be left out of every Circle's string. Give the operator a body or 'default operator $' (a hand-written body when the fields are not to be shown). The same holds for '$$' and '#?'. A field-less root, or a trait, may keep '$' abstract - there is nothing to lose.

See Q103 for abstract classes. See Q116 for default operator.

Example

defines module qa.classesandoop.abstractclassoperators

  defines class

    Shape as abstract
      name as String: String()

      Shape()
        -> shapeName as String
        name :=? shapeName

      area() as abstract
        <- rtn as Float?

      default operator ?
      default operator $

    Circle extends Shape
      radius <- 0.0

      Circle()
        -> radius as Float
        super("circle")
        this.radius: radius

      override area()
        <- rtn as Float: Float()
        pi <- 3.14159
        rtn: radius * radius * pi

      override operator ? as pure
        <- rtn as Boolean: radius?

      default operator $

  defines program

    AbstractClassOperatorsDemo()
      stdout <- Stdout()

      circle <- Circle(5.0)
      stdout.println(`Area: ${circle.area()}`)
      stdout.println(`IsSet: ${circle?}`)
      stdout.println(`Circle: ${circle}`)

      //Polymorphism with abstract type
      shapes <- List() of Shape
      shapes += Circle(3.0)
      shapes += Circle(7.5)

      for shape in shapes
        stdout.println(`Shape area: ${shape.area()}`)

Common mistakes

E07130 — Concrete subclasses must implement all abstract methods. Omitting the override triggers E07130.

Incorrect:

      //missing override of area()

Correct:

      override area()
        <- rtn as Float: Float()
        pi <- 3.14159
        rtn: radius * radius * pi

E07185 — Shape has a field ('name'), so its '$' cannot be abstract: a sub-type's generated '$' below an abstract one covers only the sub-type's own fields, and every Circle's string would silently leave 'name' out. Use 'default operator $' (or give '$' a body) on the type that has the fields; only a field-less root or a trait may keep '$' abstract. See ek9 -h E07185 for details.

Incorrect:

      default operator ?
      operator $ as pure abstract
        <- rtn as String?

    Circle extends Shape

Correct:

      default operator ?
      default operator $

    Circle extends Shape
Other ways to ask this
  • Write code for an abstract class hierarchy with Shape and Circle
  • I need an abstract Shape with area() and a concrete Circle that provides the implementation
  • In Java I'd use abstract class Shape with abstract double area(). Write the EK9 version
  • Create a Shape/Circle hierarchy showing abstract methods, override, and default operator placement
  • Can an abstract class declare operator $ as abstract when it has fields?

Coming from another language?

Java: abstract class Shape { abstract double area(); }. Python: class Shape(ABC): @abstractmethod. Rust: trait Shape { fn area(&self) -> f64; }. EK9: Shape as abstract with area() as abstract.

Keywords: circle, shape, abstract, default operator, abstract operator, class, override, area