Abstract Classes and Methods

 

Python

Java

 

When two or more classes contain redundant code, it can be factored into a common parent class.  This class is typically called an abstract class, because it is not instantiated.

 

Consider two implementations of stacks, ArrayStack and LinkedStack.  Their internal containers of elements are different, as are their methods for adding, removing, and iterating through these structures.  However, if we assume that each stack tracks its size in the same manner, the methods len and isEmpty will be the same for any implementation.  And if we assume that each implementation includes an iter method, then we can implement several other methods, such as str and addAll, just once for all stacks. 

 

The next example defines an AbstractStack class that includes the information common to all stack implementations.  To access these resources, a stack implementation, such as ArrayStack or LinkedStack, simply extends AbstractStack.   

 

class AbstractStack:

 

    def __init__(self):

        self.size = 0

 

    def isEmpty(self):

        return len(self) == 0

 

    def __len__(self):

        return self.size

   

    def __str__(self):

        result = ''

        for element in self:

            result += str(element) + '\n'

        return result

 

    def addAll(self, otherIterable):

        for element in otherIterable:

            self.push(element)              

 

AbstractStack is responsible for initializing just one instance variable, size.  Note that the str method uses a for loop over self, which implies that the stack object is iterable.  Consequently, each stack implementation must include its own definition of the iter method.  Finally, note that the addAll method copies elements from another interable object to the stack, using the push method.  Thus, the push method also must be included in each stack implementation.

 

The first implementation, ArrayStack, includes an init method that allows the programmer to specify an optional iterable object as an argument.  If this argument is not None, it is passed to the addAll method to copy its elements to the new stack.  addAll can also be called to add several elements at any subsequent point in time.  ArrayStack is also responsible for maintaining the sequence of elements.  Thus, it includes the definitions of peek, pop, push, and iter.

 

Example:

 

class ArrayStack(AbstractStack):

 

    def __init__(self, otherIterable = None):

        AbstractStack.__init__(self)

        self.items = []

        if otherIterable != None:

            self.addAll(otherIterable)

 

    def push(self, element):

        self.items.append(element)

        self.size += 1

 

    def pop(self):

        self.size -= 1

        return self.items.pop()

 

    def peek(self):

        return self.items[-1]

 

    def __iter__(self):

        probe = len(self.items) - 1

        while True:

            if probe < 0:

                raise StopIteration

            yield self.items[probe]

            probe -= 1

 

The same methods are defined in LinkedStack, but of course they access elements in a very different type of internal structure.

 

class OneWayNode:

 

    def __init__(self, data, next):

        self.data = data

        self.next = next

 

class LinkedStack(AbstractStack):

 

    def __init__(self, otherIterable = None):

        AbstractStack.__init__(self)

        self.items = None

        if otherIterable != None:

            self.addAll(otherIterable)

 

    def push(self, element):

        self.items = OneWayNode(element, self.items)

        self.size += 1

 

    def pop(self):

        element = self.items.data

        self.items = self.items.next

        self.size -= 1

        return element

 

    def peek(self):

        return self.items.data

 

    def __iter__(self):

        probe = self.items

        while True:

            if probe is None:

                raise StopIteration

            yield probe.data

            probe = probe.next

 

 

 

 

When two or more classes contain redundant code, it can be factored into a common parent class.  This class is typically called an abstract class, because it is not instantiated.

 

Consider two implementations of stacks, ArrayStack and LinkedStack.  Their internal containers of elements are different, as are their methods for adding, removing, and iterating through these structures.  However, if we assume that each stack tracks its size in the same manner, the methods len and isEmpty will be the same for any implementation.  And if we assume that each implementation includes an iter method, then we can implement several other methods, such as str and addAll, just once for all stacks. 

 

The next example defines an AbstractStack class that includes the information common to all stack implementations. To access these resources, a stack implementation, such as ArrayStack or LinkedStack, simply extends AbstractStack.  This class is specified as abstract with the reserved word abstract.  The class must also include all of the methods in the TrueStack interface.  The methods that must be defined in the subclasses  are specified as abstract methods, again with the reserved word abstract.

 

import java.util.*;

 

abstract public AbstractStack<E> implements TrueStack<E>{

 

    protected int size, modCount;

 

    public AbstractStack(){

        this.size = 0;

        this.modCount = 0;

    }

 

    public boolean empty(){

        return this.size() == 0;

    }

 

    public int size(){

        return size;

    }

 

    public String toString(){

        String result = '';

        for (E element : this)

            result += str(element) + '\n';

        return result;

    }

 

    public void addAll(Collection<E> col){

        for (E element : col)

            this.push(element);

    }

 

    abstract public E peek();

 

    abstract public E pop();

 

    abstract public void push(E element){

 

    abstract public Iterator<E> iterator();

}

 

AbstractStack is responsible for initializing two instance variables, size and modCount.  Note that the toString method uses a for loop over this, which implies that the stack object is iterable.  Consequently, each stack implementation must include its own definition of the iterator method.  Finally, note that the addAll method copies elements from any Collection object to the stack.  Any Collection object recognizes the iterator method, and thus can be used with a for loop.

 

The first implementation, ArrayStack, includes two constructors.  One allows the programmer to specify a Collection object as an argument.  This object is passed to the addAll method to copy its elements to the new stack.  addAll can also be called to add several elements at any subsequent point in time.  ArrayStack is also responsible for maintaining the sequence of elements.  Thus, it includes the definitions of peek, pop, push, and iterator.

 

import java.util.*;

 

public class ArrayStack<E> extends AbstractStack<E>{

 

    private List<E> list;

 

    public ArrayStack(){

        super();

        this.list = new ArrayList<E>();

    }

 

    public ArrayStack(Collection<E> col){

        this();

        this.addAll(col);

    }

 

    public E peek(){

        if (this.isEmpty())

            throw new EmptyStackException();

        return list.get(this.size() - 1);

    }

 

    public E pop(){

        if (this.isEmpty())

            throw new EmptyStackException();

        this.size -= 1;

        this.modCount += 1;

        return this.list.remove(this.size());

    }

 

    public void push(E element){

        this.list.add(element);

        this.size += 1;

        this.modCount += 1;

    }

 

    public Iterator<E> iterator(){

        return new StackIterator<E>();                

    }

 

    private class StackIterator<E> implements Iterator<E>{

 

        private int curPos;

        private int curModCount;

 

        private StackIterator(){

            this.curPos = this.size() - 1;

            this.curModCount = modCount;

        }

 

        public boolean hasNext(){

            return curPos >= 0;

        }

 

        public E next(){

            if (! this.hasNext()

                throw new IllegalStateException();

            if (this.curModCount != modCount)

                throw new ConcurrentModificationException()

            E data = list.get(this.curPos);

            this.curPos -= 1;

            return data;

        }

 

        public void remove(){

            throw new UnsupportedOperationException();

        }

    }                                

}

 

The same methods are defined in LinkedStack, but of course they access elements in a very different type of internal structure.

 

import java.util.iterator;

 

public class LinkedStack<E> extends AbstractStack<E>{

 

    private OneWayNode<E> items;

 

    public LinkedStack(){

        super();

        this.items = null;

    }

 

    public LinkedStack(Collection<E> col){

        this();

        this.addAll(col);

    }

 

    public void push(E element){

        this.items = new OneWayNode<E>(element, items);

        this.size += 1;

        this.modCount += 1;

    }

   

    public E pop(){

        E element = this.items.data;

        this.items = this.items.next;

        this.size -= 1;

        this.modCount += 1;

        return element;

    }

 

    public E peek(){

        return this.items.data;

    }

 

    public Iterator<E> iterator(){

        return new StackIterator<E>();                

    }

 

    private class StackIterator<E> implements Iterator<E>{

 

        private OneWayNode curPos;

        private int curModCount;

 

        private StackIterator(){

            this.curPos = items;

            this.curModCount = modCount;

        }

 

        public boolean hasNext(){

            return curPos != null;

        }

 

        public E next(){

            if (! this.hasNext()

                throw new IllegalStateException();

            if (this.curModCount != modCount)

                throw new ConcurrentModificationException()

            E data = this.curPos.data;

            this.curPos = this.curPos.next();

            return data;

        }

 

        public void remove(){

            throw new UnsupportedOperationException();

        }

    }                               

 

    private class OneWayNode<E>{

 

        private E data;

        private OneWayNode next;

 

        private OneWayNode(E data, OneWayNode next){

            this.data = data;

            This.next = next;

        }

    }

}

 

Previous

Index

Next