Вы здесь почти ничего осмысленного из ООП не используете. Пишу пример на паскале, но это ничего не меняет.
Стандартным решением было бы создать базовый класс "Рисуемый", тот, который может быть отрисован на канве.
Допустим TSprite. И объявим виртуальный метод Draw, чтобы мы могли попросить отрисываться.
TSprite = class //ну или интерфейс
public
procedure Draw; virtual; abstract;
end;
Теперь напишем класс TRectangle на основе класса TSprite. Где тоже объявим метод Draw, но напишем там уже отрисовку. Ну и объявим приватные поля размеров и позиции с конструктором.
TRectangle = class(TSprite)
private
FX, FY, FH, FW: Float;
FCanvas: TCanvas;
public
procedure Draw; override;
constructor Create(Canvas: TCanvas; const X, Y, H, W: Float);
end;
constructor TRectangle.Create(Canvas: TCanvas; const X, Y, H, W: Float);
begin
FCanvas := Canvas;
FX := X;
FY := Y;
FH := H;
FW := W;
end;
procedure TRectangle.Draw;
begin
FCanvas.Rectangle(FX, FY, FH, FW); // Это отрисовка фигуры на канве
end;
Теперь мы можем создавать класс TRectangle, указывать целевую канву и размеры.
Rect := TRectangle.Create(Canvas, 10, 10, 40, 45);
Rect.Draw; // Всё, можно отрисовать
После всего этого мы можем создать другие фигуры или объекты, которые тоже будут наследоваться от TSprite. Сможем поместить их в список объектов типа TSprite и обращаться к каждому объекту через Draw. Например, в цикле отрисовки всех объектов.