SCADA System OOP Design Analytical Test

Graduate Electrical Engineering - Software Engineering Unit

Analyze how Object-Oriented Programming principles can be applied to the design of a Supervisory Control and Data Acquisition (SCADA) system for a power grid.

Abstraction

Modeling essential characteristics

Encapsulation

Bundling data with methods

Inheritance

Creating hierarchical relationships

Polymorphism

One interface, multiple implementations

Instructions: Analyze each question carefully. Consider how each OOP pillar can address specific design challenges in a SCADA system for power grid management. Click the buttons to reveal model answers.
1

Abstraction in SCADA Component Modeling

In a SCADA system for power transmission, you need to model various grid components: transformers, circuit breakers, transmission lines, and busbars. Each has unique properties and behaviors but also shares common characteristics. How would you apply abstraction to design a class hierarchy that captures the essential features of these components while hiding implementation details?

Model Answer

Create an abstract base class GridComponent that defines the essential interface for all power grid components:

public abstract class GridComponent { protected String id; protected String location; protected ComponentStatus status; // Abstract methods defining essential behaviors public abstract Measurement getMeasurement(); public abstract boolean executeCommand(ControlCommand command); public abstract Alarm checkAlarms(); // Concrete methods for common functionality public String getComponentInfo() { return "ID: " + id + ", Location: " + location + ", Status: " + status; } // Getters and setters omitted for brevity }

This abstraction:

  1. Hides the complex implementation details of each specific component type
  2. Defines a common interface for monitoring and control operations
  3. Allows the SCADA system to treat all components uniformly through the abstract interface
  4. Simplifies the system design by focusing on what components do, not how they do it internally
2

Encapsulation for Grid Security

Circuit breakers in a power grid have critical safety parameters (trip current, time delay) that should not be modified arbitrarily. How would you use encapsulation to protect these parameters while still allowing authorized control actions through the SCADA system?

Model Answer

Create a CircuitBreaker class that encapsulates its critical parameters and provides controlled access through validation methods:

public class CircuitBreaker extends GridComponent { private double tripCurrent; // Protected from direct access private double timeDelay; // Protected from direct access private boolean autoRecloseEnabled; // Protected from direct access private BreakerType type; // Constructor with validation public CircuitBreaker(String id, double tripCurrent, double timeDelay) { this.id = id; setTripCurrent(tripCurrent); // Use setter for validation setTimeDelay(timeDelay); } // Encapsulated setters with safety validation public void setTripCurrent(double current) { if (current < 0) { throw new IllegalArgumentException("Trip current cannot be negative"); } if (current > getMaxAllowableCurrent()) { throw new SecurityException("Trip current exceeds safety limits"); } this.tripCurrent = current; logParameterChange("tripCurrent", current); } public void setTimeDelay(double delay) { if (delay < MIN_TIME_DELAY || delay > MAX_TIME_DELAY) { throw new IllegalArgumentException("Time delay outside permitted range"); } this.timeDelay = delay; logParameterChange("timeDelay", delay); } // Public method for authorized control actions public void trip() { if (validateTripCommand()) { executeInternalTripSequence(); status = ComponentStatus.TRIPPED; notifySCADA(); } } // Private helper methods - hidden implementation private void executeInternalTripSequence() { // Complex trip logic encapsulated here disconnectContacts(); arcSuppression(); updateFaultLog(); } // Getters with access control public double getTripCurrent() { return requiresAuthorization() ? getAuthorizedValue("tripCurrent") : tripCurrent; } }

This encapsulation approach:

  • Protects critical parameters from unsafe modifications
  • Centralizes validation logic in one place
  • Logs all parameter changes for audit trails
  • Hides complex trip sequence implementation from external code
  • Allows for future changes to internal logic without affecting SCADA clients
3

Inheritance for Sensor Hierarchy

A SCADA system needs to interface with various types of sensors: voltage sensors, current sensors, temperature sensors, and vibration sensors. Each sensor type has unique calibration methods and data processing requirements, but all share common functionality like data sampling and communication. Design an inheritance hierarchy for these sensors.

Model Answer

// Base abstract class for all sensors public abstract class Sensor { protected String sensorId; protected double samplingRate; protected SensorStatus status; protected LocalDateTime lastCalibration; public Sensor(String sensorId) { this.sensorId = sensorId; } // Common functionality for all sensors public abstract double readValue(); public abstract void calibrate(); public void setSamplingRate(double rate) { this.samplingRate = rate; configureHardwareSampling(rate); } public boolean needsCalibration() { return lastCalibration.isBefore(LocalDateTime.now().minusMonths(6)); } protected abstract void configureHardwareSampling(double rate); } // Intermediate abstract class for electrical sensors public abstract class ElectricalSensor extends Sensor { protected double rangeMin; protected double rangeMax; protected double accuracy; public ElectricalSensor(String sensorId, double rangeMin, double rangeMax) { super(sensorId); this.rangeMin = rangeMin; this.rangeMax = rangeMax; } public boolean isWithinRange(double value) { return value >= rangeMin && value <= rangeMax; } // Electrical sensors share common calibration procedure @Override public void calibrate() { performZeroCalibration(); performGainCalibration(); lastCalibration = LocalDateTime.now(); } protected abstract void performZeroCalibration(); protected abstract void performGainCalibration(); } // Concrete classes for specific sensor types public class VoltageSensor extends ElectricalSensor { private double phaseAngle; private TransformerRatio ratio; public VoltageSensor(String sensorId, double rangeMin, double rangeMax, TransformerRatio ratio) { super(sensorId, rangeMin, rangeMax); this.ratio = ratio; } @Override public double readValue() { double rawValue = readADCValue(); return applyTransformerRatio(rawValue); } @Override protected void configureHardwareSampling(double rate) { configureVoltageADC(rate); } @Override protected void performZeroCalibration() { // Voltage-specific zero calibration shortCircuitCalibration(); } @Override protected void performGainCalibration() { // Voltage-specific gain calibration referenceVoltageCalibration(); } // VoltageSensor-specific methods public double calculateRMS() { // RMS calculation specific to voltage measurements } } public class TemperatureSensor extends Sensor { private TemperatureUnit unit; private ThermocoupleType thermocoupleType; public TemperatureSensor(String sensorId, ThermocoupleType type) { super(sensorId); this.thermocoupleType = type; } @Override public double readValue() { double voltage = readThermocoupleVoltage(); return convertToTemperature(voltage, thermocoupleType); } @Override public void calibrate() { // Temperature-specific calibration icePointCalibration(); boilingPointCalibration(); lastCalibration = LocalDateTime.now(); } @Override protected void configureHardwareSampling(double rate) { configureThermocoupleADC(rate); } }

This inheritance hierarchy:

  • Eliminates code duplication by placing common functionality in base classes
  • Creates a logical taxonomy of sensor types
  • Allows for specialized behavior in derived classes while maintaining a common interface
  • Makes the system extensible - new sensor types can be added easily
4

Polymorphism for Alarm Handling

A SCADA system generates various types of alarms: overvoltage, undervoltage, overcurrent, equipment failure, and communication loss. Each alarm type requires different handling procedures, priority levels, and notification methods. How would you use polymorphism to create a unified alarm handling system?

Model Answer

// Base alarm interface or abstract class public abstract class Alarm { protected String alarmId; protected LocalDateTime triggerTime; protected AlarmSeverity severity; protected String sourceComponent; protected boolean acknowledged; public Alarm(String alarmId, String sourceComponent) { this.alarmId = alarmId; this.sourceComponent = sourceComponent; this.triggerTime = LocalDateTime.now(); } // Polymorphic methods - different implementations for each alarm type public abstract String getDescription(); public abstract List getRecommendedActions(); public abstract void executeAutoResponse(); public abstract AlarmSeverity calculateSeverity(); // Common functionality public void acknowledge(String operator) { this.acknowledged = true; logAcknowledgement(operator); } public boolean requiresImmediateAttention() { return calculateSeverity().ordinal() >= AlarmSeverity.HIGH.ordinal(); } } // Concrete alarm implementations public class OvervoltageAlarm extends Alarm { private double measuredVoltage; private double thresholdVoltage; private double durationExceeded; public OvervoltageAlarm(String sourceComponent, double measuredVoltage, double threshold) { super(generateAlarmId("OVERVOLTAGE"), sourceComponent); this.measuredVoltage = measuredVoltage; this.thresholdVoltage = threshold; } @Override public String getDescription() { return String.format("Overvoltage at %s: %.2f kV exceeds threshold %.2f kV for %.1f seconds", sourceComponent, measuredVoltage, thresholdVoltage, durationExceeded); } @Override public List getRecommendedActions() { return Arrays.asList( "Check transformer tap settings", "Verify capacitor bank status", "Consider load shedding if persistent", "Inspect for fault conditions" ); } @Override public void executeAutoResponse() { // Overvoltage-specific automatic response if (measuredVoltage > thresholdVoltage * 1.1) { initiateTapChangeDown(); } if (measuredVoltage > thresholdVoltage * 1.2) { initiateLoadShedding(); } } @Override public AlarmSeverity calculateSeverity() { double percentageOver = ((measuredVoltage - thresholdVoltage) / thresholdVoltage) * 100; if (percentageOver > 20) return AlarmSeverity.CRITICAL; if (percentageOver > 10) return AlarmSeverity.HIGH; if (percentageOver > 5) return AlarmSeverity.MEDIUM; return AlarmSeverity.LOW; } } public class CommunicationLossAlarm extends Alarm { private Duration outageDuration; private String communicationProtocol; public CommunicationLossAlarm(String sourceComponent, String protocol) { super(generateAlarmId("COMM_LOSS"), sourceComponent); this.communicationProtocol = protocol; } @Override public String getDescription() { return String.format("Communication loss with %s using %s protocol for %d minutes", sourceComponent, communicationProtocol, outageDuration.toMinutes()); } @Override public List getRecommendedActions() { return Arrays.asList( "Check physical communication links", "Verify RTU power supply", "Restart communication driver", "Switch to backup communication channel if available" ); } @Override public void executeAutoResponse() { // Communication loss-specific automatic response switchToBackupChannel(); bufferLocalData(); attemptReconnection(); } @Override public AlarmSeverity calculateSeverity() { if (outageDuration.toHours() > 1) return AlarmSeverity.HIGH; if (outageDuration.toMinutes() > 15) return AlarmSeverity.MEDIUM; return AlarmSeverity.LOW; } } // Unified alarm handler using polymorphism public class AlarmHandler { private List activeAlarms = new ArrayList<>(); public void processAlarm(Alarm alarm) { // Polymorphic call - different behavior for each alarm type alarm.executeAutoResponse(); // Common handling for all alarms activeAlarms.add(alarm); logAlarm(alarm); if (alarm.requiresImmediateAttention()) { notifyControlRoom(alarm); } // Display uses polymorphic getDescription() displayAlarm(alarm.getDescription(), alarm.calculateSeverity()); } public void handleAllAlarms(List alarms) { for (Alarm alarm : alarms) { processAlarm(alarm); // Single interface, multiple behaviors } } }

This polymorphic design:

  • Allows the alarm handler to treat all alarms uniformly through the base Alarm interface
  • Enables type-specific behavior through method overriding
  • Makes it easy to add new alarm types without modifying existing alarm handling code
  • Centralizes common alarm functionality while allowing specialization
  • Supports the Open/Closed Principle - open for extension, closed for modification
5

Abstraction for Control Commands

SCADA operators need to send different types of control commands: breaker operations (open/close), transformer tap changes, capacitor bank switching, and setpoint adjustments. Design an abstraction that allows a unified command interface while hiding the implementation details of each command type.

Model Answer

// Abstract command interface public interface ControlCommand { String getCommandId(); CommandStatus execute(); boolean validate(); String getTargetComponent(); CommandType getCommandType(); CommandPriority getPriority(); // Default method (Java 8+ feature) providing common behavior default CommandLogEntry createLogEntry() { return new CommandLogEntry( getCommandId(), getTargetComponent(), getCommandType(), LocalDateTime.now() ); } } // Abstract base class providing common implementation public abstract class AbstractControlCommand implements ControlCommand { protected String commandId; protected String targetComponent; protected CommandPriority priority; protected String operatorId; public AbstractControlCommand(String targetComponent, String operatorId) { this.commandId = generateCommandId(); this.targetComponent = targetComponent; this.operatorId = operatorId; this.priority = CommandPriority.NORMAL; } @Override public String getCommandId() { return commandId; } @Override public String getTargetComponent() { return targetComponent; } @Override public boolean validate() { // Common validation for all commands if (targetComponent == null || targetComponent.isEmpty()) { return false; } if (!isOperatorAuthorized(operatorId, this)) { return false; } return performCommandSpecificValidation(); } // Abstract method for command-specific validation protected abstract boolean performCommandSpecificValidation(); // Template method pattern - defines algorithm skeleton @Override public final CommandStatus execute() { if (!validate()) { return CommandStatus.VALIDATION_FAILED; } try { preExecutionChecks(); CommandStatus status = executeImpl(); // Abstract - different per command type postExecutionActions(status); return status; } catch (CommandExecutionException e) { handleExecutionFailure(e); return CommandStatus.FAILED; } } protected abstract CommandStatus executeImpl(); protected abstract void preExecutionChecks(); protected abstract void postExecutionActions(CommandStatus status); protected abstract void handleExecutionFailure(CommandExecutionException e); } // Concrete command implementations public class BreakerOperationCommand extends AbstractControlCommand { private BreakerOperation operation; // OPEN or CLOSE private boolean withSynchronismCheck; public BreakerOperationCommand(String breakerId, BreakerOperation operation, String operatorId, boolean withSyncCheck) { super(breakerId, operatorId); this.operation = operation; this.withSynchronismCheck = withSyncCheck; } @Override protected boolean performCommandSpecificValidation() { BreakerStatus currentStatus = getBreakerStatus(targetComponent); // Can't open an already open breaker, or close an already closed one if ((operation == BreakerOperation.OPEN && currentStatus == BreakerStatus.OPEN) || (operation == BreakerOperation.CLOSE && currentStatus == BreakerStatus.CLOSED)) { return false; } // Additional safety checks if (operation == BreakerOperation.CLOSE) { return validateCloseConditions(); } return true; } @Override protected void preExecutionChecks() { verifyBreakerHealth(); if (withSynchronismCheck && operation == BreakerOperation.CLOSE) { performSynchronismCheck(); } } @Override protected CommandStatus executeImpl() { BreakerController controller = getBreakerController(targetComponent); if (operation == BreakerOperation.OPEN) { return controller.openBreaker(); } else { return controller.closeBreaker(withSynchronismCheck); } } @Override public CommandType getCommandType() { return CommandType.BREAKER_OPERATION; } // Additional methods specific to breaker operations private boolean validateCloseConditions() { // Complex validation logic for closing a breaker return checkVoltageDifference() && checkFrequencyDifference() && checkPhaseAngle(); } } // Command executor using abstraction public class CommandExecutor { public CommandResult executeCommand(ControlCommand command) { CommandLogEntry logEntry = command.createLogEntry(); CommandStatus status = command.execute(); // Polymorphic call logEntry.setCompletionStatus(status); logEntry.setCompletionTime(LocalDateTime.now()); auditLog.add(logEntry); return new CommandResult(command.getCommandId(), status); } public List executeCommandSequence(List commands) { List results = new ArrayList<>(); for (ControlCommand command : commands) { // All commands treated uniformly through the ControlCommand interface results.add(executeCommand(command)); } return results; } }

This abstraction provides:

  • A clean separation between command definition and execution
  • Ability to add new command types without changing the command executor
  • Consistent validation, logging, and error handling across all commands
  • Hiding of complex, command-specific implementation details
  • A template method pattern that ensures consistent execution flow
6

Encapsulation in Data Acquisition

Raw data from field devices (RTUs, IEDs) comes in various protocols (IEC 61850, DNP3, Modbus) and formats. How would you use encapsulation to hide protocol-specific details while providing a unified data interface to the SCADA application layer?

Model Answer

// Encapsulated protocol handler interface public interface ProtocolHandler { List pollDevice(String deviceAddress); boolean sendCommand(String deviceAddress, ControlCommand command); DeviceStatus getDeviceStatus(String deviceAddress); void configure(ProtocolConfiguration config); // Protocol connection management (encapsulated) boolean connect(); void disconnect(); boolean isConnected(); } // Base class encapsulating common protocol functionality public abstract class AbstractProtocolHandler implements ProtocolHandler { protected ProtocolConfiguration config; protected Connection connection; protected ProtocolStatistics statistics; @Override public void configure(ProtocolConfiguration config) { this.config = config; initializeProtocolSpecificSettings(config); } @Override public boolean connect() { if (connection != null && connection.isConnected()) { return true; } try { connection = establishConnection(); // Abstract method statistics.connectionAttempts++; return true; } catch (ProtocolException e) { logConnectionFailure(e); statistics.failedConnections++; return false; } } @Override public void disconnect() { if (connection != null) { gracefullyCloseConnection(connection); // Protocol-specific connection = null; } } // Protocol-specific details encapsulated in abstract methods protected abstract Connection establishConnection() throws ProtocolException; protected abstract void gracefullyCloseConnection(Connection connection); protected abstract void initializeProtocolSpecificSettings(ProtocolConfiguration config); // Encapsulated common data validation protected boolean validateDataPoint(DataPoint point) { if (point == null) return false; if (point.getTimestamp() == null) return false; if (point.getValue() == null) return false; if (!isValueInRange(point)) return false; return true; } private boolean isValueInRange(DataPoint point) { // Range checking logic encapsulated here DataPointDefinition def = getDataPointDefinition(point.getId()); return def.getMinValue() <= point.getValue() && point.getValue() <= def.getMaxValue(); } } // IEC 61850 implementation with encapsulated MMS details public class IEC61850Handler extends AbstractProtocolHandler { private MmsClient mmsClient; private IedModel iedModel; @Override protected Connection establishConnection() throws ProtocolException { // Encapsulate all IEC 61850 MMS protocol details mmsClient = new MmsClient(config.getIpAddress(), config.getPort()); mmsClient.setTimeout(config.getTimeout()); // Complex MMS association establishment MmsAssociation association = mmsClient.associate(); if (!association.isEstablished()) { throw new ProtocolException("MMS association failed"); } // Load IED model (ICD file parsing encapsulated) iedModel = loadIedModel(config.getIedModelPath()); return new IEC61850Connection(mmsClient, association, iedModel); } @Override public List pollDevice(String deviceAddress) { List points = new ArrayList<>(); // Encapsulated IEC 61850 data retrieval for (LogicalNode ln : iedModel.getLogicalNodes()) { for (DataObject dataObj : ln.getDataObjects()) { // Complex MMS read operation hidden MmsValue mmsValue = mmsClient.read(dataObj.getReference()); // Convert to generic DataPoint (protocol details hidden) DataPoint point = convertToDataPoint(dataObj, mmsValue); if (validateDataPoint(point)) { points.add(point); } } } statistics.pollsCompleted++; return points; } // Private conversion method - encapsulation private DataPoint convertToDataPoint(DataObject dataObj, MmsValue mmsValue) { // Complex conversion logic hidden Object value = extractValueFromMms(mmsValue); DataQuality quality = extractQualityFromMms(mmsValue); return new DataPoint( dataObj.getReference(), value, quality, LocalDateTime.now(), dataObj.getUnits() ); } // More IEC 61850 specific encapsulated methods... } // DNP3 implementation with encapsulated DNP3 details public class DNP3Handler extends AbstractProtocolHandler { private Dnp3Master master; private Dnp3Stack stack; @Override protected Connection establishConnection() throws ProtocolException { // Encapsulate all DNP3 protocol details Dnp3Config config = createDnp3Config(this.config); stack = new Dnp3Stack(config); master = stack.createMaster(); // Complex DNP3 link layer initialization master.enable(); return new DNP3Connection(master, stack); } @Override public List pollDevice(String deviceAddress) { List points = new ArrayList<>(); // Encapsulated DNP3 data retrieval Dnp3Poll poll = createPollRequest(); Dnp3Response response = master.poll(poll); // Extract data from response (DNP3 details hidden) for (Dnp3Measurement meas : response.getMeasurements()) { DataPoint point = convertToDataPoint(meas); if (validateDataPoint(point)) { points.add(point); } } return points; } // Private conversion method - encapsulation private DataPoint convertToDataPoint(Dnp3Measurement meas) { // Complex DNP3 to internal format conversion return new DataPoint( meas.getPointIndex(), meas.getValue(), convertDnp3Quality(meas.getQuality()), meas.getTimestamp(), getUnitsForPoint(meas.getPointIndex()) ); } } // Unified data acquisition service using encapsulation public class DataAcquisitionService { private Map protocolHandlers = new HashMap<>(); public List acquireDataFromDevice(String deviceId) { DeviceInfo device = deviceRegistry.getDevice(deviceId); ProtocolHandler handler = protocolHandlers.get(device.getProtocolType()); if (handler == null) { handler = createProtocolHandler(device.getProtocolType()); protocolHandlers.put(device.getProtocolType(), handler); } // Unified interface - protocol details completely hidden return handler.pollDevice(device.getAddress()); } }

This encapsulation approach:

  • Hides complex protocol-specific implementation details behind a clean interface
  • Allows protocol implementations to be changed or updated without affecting the SCADA application
  • Centralizes protocol-specific error handling and recovery
  • Provides a consistent data model regardless of the underlying protocol
  • Makes the system more maintainable and testable (protocol handlers can be mocked)
7

Inheritance for Protection Relays

Different types of protection relays (overcurrent, distance, differential) share common characteristics but have unique protection algorithms. Design an inheritance hierarchy that models these relay types while maximizing code reuse.

Model Answer

// Base class for all protection relays public abstract class ProtectionRelay { protected String relayId; protected RelaySettings settings; protected RelayStatus status; protected List zones; public ProtectionRelay(String relayId) { this.relayId = relayId; this.settings = new RelaySettings(); this.status = RelayStatus.IN_SERVICE; } // Common functionality for all relays public void enable() { status = RelayStatus.IN_SERVICE; initializeProtectionElements(); } public void disable() { status = RelayStatus.OUT_OF_SERVICE; deactivateProtectionElements(); } public TripDecision evaluateConditions(Measurements measurements) { if (status != RelayStatus.IN_SERVICE) { return TripDecision.NO_TRIP; } // Common preprocessing Measurements filtered = applyFilters(measurements); // Delegates to specific protection algorithm return evaluateProtectionAlgorithm(filtered); } public void reset() { clearTimers(); resetFlags(); } // Abstract methods for relay-specific behavior protected abstract TripDecision evaluateProtectionAlgorithm(Measurements measurements); protected abstract void initializeProtectionElements(); protected abstract void deactivateProtectionElements(); // Common protected methods for reuse protected boolean isFaultInZone(Measurement measurement, ProtectionZone zone) { // Common zone checking logic return zone.contains(measurement) && measurement.exceedsThreshold(settings); } protected void startTimer(String timerId) { // Common timer management TimerManager.getInstance().startTimer(timerId, settings.getTimeDelay()); } protected boolean timerExpired(String timerId) { return TimerManager.getInstance().isExpired(timerId); } } // Intermediate class for overcurrent protection family public abstract class OvercurrentRelay extends ProtectionRelay { protected CurrentThreshold pickupCurrent; protected TimeCurrentCurve curve; protected boolean directional; public OvercurrentRelay(String relayId, CurrentThreshold pickup, TimeCurrentCurve curve) { super(relayId); this.pickupCurrent = pickup; this.curve = curve; } @Override protected void initializeProtectionElements() { initializeCurrentElements(); if (directional) { initializeDirectionalElement(); } } // Common overcurrent relay methods protected boolean isAbovePickup(CurrentMeasurement current) { return current.getValue() > pickupCurrent.getValue(); } protected double calculateTimeDelay(CurrentMeasurement current) { return curve.getTimeDelay(current.getValue() / pickupCurrent.getValue()); } // Abstract methods for specific overcurrent relay types protected abstract void initializeCurrentElements(); protected abstract void initializeDirectionalElement(); } // Concrete overcurrent relay implementations public class InstantaneousOvercurrentRelay extends OvercurrentRelay { public InstantaneousOvercurrentRelay(String relayId, CurrentThreshold pickup) { super(relayId, pickup, TimeCurrentCurve.INSTANTANEOUS); } @Override protected TripDecision evaluateProtectionAlgorithm(Measurements measurements) { CurrentMeasurement current = measurements.getCurrent(); if (isAbovePickup(current)) { // Instantaneous trip - no time delay return TripDecision.TRIP; } return TripDecision.NO_TRIP; } @Override protected void initializeProtectionElements() { super.initializeProtectionElements(); // Instantaneous relay specific initialization configureHighSpeedTrip(); } @Override protected void initializeCurrentElements() { // Initialize current sensing elements configureCTInputs(); } @Override protected void initializeDirectionalElement() { // Not applicable for non-directional relay } @Override protected void deactivateProtectionElements() { deactivateHighSpeedTrip(); } } public class InverseTimeOvercurrentRelay extends OvercurrentRelay { private String timingTimerId; public InverseTimeOvercurrentRelay(String relayId, CurrentThreshold pickup, TimeCurrentCurve curve) { super(relayId, pickup, curve); this.timingTimerId = relayId + "_TIMER"; } @Override protected TripDecision evaluateProtectionAlgorithm(Measurements measurements) { CurrentMeasurement current = measurements.getCurrent(); if (isAbovePickup(current)) { if (!timerExpired(timingTimerId)) { // Timer already running return TripDecision.NO_TRIP; } double timeDelay = calculateTimeDelay(current); startTimer(timingTimerId); if (timerExpired(timingTimerId)) { return TripDecision.TRIP; } } else { // Current below pickup - reset timer TimerManager.getInstance().resetTimer(timingTimerId); } return TripDecision.NO_TRIP; } // Additional inverse-time specific methods... } // Distance relay implementation (different inheritance branch) public abstract class DistanceRelay extends ProtectionRelay { protected ImpedanceSetting zoneSettings; protected List impedanceZones; public DistanceRelay(String relayId, ImpedanceSetting zoneSettings) { super(relayId); this.zoneSettings = zoneSettings; this.impedanceZones = createImpedanceZones(zoneSettings); } @Override protected TripDecision evaluateProtectionAlgorithm(Measurements measurements) { ImpedanceMeasurement impedance = calculateImpedance( measurements.getVoltage(), measurements.getCurrent() ); for (ProtectionZone zone : impedanceZones) { if (zone.contains(impedance)) { return evaluateZoneTrip(zone, impedance, measurements); } } return TripDecision.NO_TRIP; } protected abstract TripDecision evaluateZoneTrip(ProtectionZone zone, ImpedanceMeasurement impedance, Measurements measurements); protected abstract ImpedanceMeasurement calculateImpedance(VoltageMeasurement voltage, CurrentMeasurement current); } // Concrete distance relay public class MhoDistanceRelay extends DistanceRelay { public MhoDistanceRelay(String relayId, ImpedanceSetting zoneSettings) { super(relayId, zoneSettings); } @Override protected TripDecision evaluateZoneTrip(ProtectionZone zone, ImpedanceMeasurement impedance, Measurements measurements) { // Mho characteristic evaluation double zoneTimeDelay = zoneSettings.getTimeDelay(zone.getZoneNumber()); if (timerExpired(relayId + "_ZONE" + zone.getZoneNumber())) { return TripDecision.TRIP; } startTimer(relayId + "_ZONE" + zone.getZoneNumber()); return TripDecision.NO_TRIP; } @Override protected ImpedanceMeasurement calculateImpedance(VoltageMeasurement voltage, CurrentMeasurement current) { // Mho-specific impedance calculation with compensation return new ImpedanceMeasurement( voltage.getValue() / current.getValue(), calculatePhaseAngle(voltage, current) ); } } // Relay management using inheritance hierarchy public class RelayCoordinator { private List relays = new ArrayList<>(); public void evaluateAllRelays(GridMeasurements measurements) { for (ProtectionRelay relay : relays) { // Polymorphic call - different behavior for each relay type TripDecision decision = relay.evaluateConditions( measurements.getForRelay(relay.getRelayId()) ); if (decision == TripDecision.TRIP) { executeTripSequence(relay); } } } public void addRelay(ProtectionRelay relay) { relays.add(relay); relay.enable(); } }

This inheritance hierarchy provides:

  • Maximum code reuse through inheritance of common relay functionality
  • Clear categorization of relay types with appropriate abstraction levels
  • Ability to add new relay types with minimal code duplication
  • Consistent interface for relay coordination and management
  • Specialized behavior where needed while maintaining common operational patterns
8

Polymorphism for Data Visualization

A SCADA system needs to display different types of data (real-time values, historical trends, alarm lists, single-line diagrams) in various visualization formats. How would you use polymorphism to create a flexible visualization system that can render different data types without conditional logic?

Model Answer

// Base visualization interface public interface DataVisualizer { void render(DisplayCanvas canvas); void updateData(VisualizationData data); Dimension getPreferredSize(); void applyTheme(DisplayTheme theme); List getSupportedInteractions(); } // Base class for common visualization functionality public abstract class AbstractVisualizer implements DataVisualizer { protected VisualizationData data; protected DisplayConfig config; protected DisplayTheme theme; public AbstractVisualizer(VisualizationData data, DisplayConfig config) { this.data = data; this.config = config; } @Override public void updateData(VisualizationData newData) { this.data = newData; onDataUpdated(); requestRedraw(); } @Override public void applyTheme(DisplayTheme theme) { this.theme = theme; configureColorsFromTheme(theme); } // Template method pattern @Override public final void render(DisplayCanvas canvas) { canvas.clear(); renderBackground(canvas); renderData(canvas); // Abstract - different per visualizer type renderOverlays(canvas); renderBorder(canvas); } protected abstract void renderData(DisplayCanvas canvas); protected abstract void onDataUpdated(); // Common rendering methods protected void renderBackground(DisplayCanvas canvas) { canvas.setColor(theme.getBackgroundColor()); canvas.fillRect(0, 0, canvas.getWidth(), canvas.getHeight()); } protected void renderBorder(DisplayCanvas canvas) { canvas.setColor(theme.getBorderColor()); canvas.drawRect(0, 0, canvas.getWidth() - 1, canvas.getHeight() - 1); } } // Real-time value visualizer public class RealTimeValueVisualizer extends AbstractVisualizer { private ValueDisplayFormat format; private double lastValue; private LocalDateTime lastUpdate; public RealTimeValueVisualizer(RealTimeData data, DisplayConfig config) { super(data, config); this.format = ValueDisplayFormat.NUMERIC; } @Override protected void renderData(DisplayCanvas canvas) { RealTimeData rtData = (RealTimeData) data; double currentValue = rtData.getCurrentValue(); // Real-time specific rendering String displayText = formatValue(currentValue, format); Color textColor = getValueColor(currentValue); canvas.setColor(textColor); canvas.setFont(config.getFont()); canvas.drawTextCentered(displayText, canvas.getWidth() / 2, canvas.getHeight() / 2); // Optional trend indicator if (lastValue != 0) { renderTrendIndicator(canvas, currentValue, lastValue); } lastValue = currentValue; lastUpdate = LocalDateTime.now(); } @Override protected void onDataUpdated() { // Real-time specific update handling if (data instanceof RealTimeData) { RealTimeData rtData = (RealTimeData) data; checkForAlarmConditions(rtData.getCurrentValue()); } } @Override public List getSupportedInteractions() { return Arrays.asList( DisplayInteraction.ZOOM, DisplayInteraction.CHANGE_UNITS, DisplayInteraction.SHOW_HISTORY_POPUP ); } // Real-time specific methods private Color getValueColor(double value) { if (value > config.getHighLimit()) return theme.getAlarmColor(); if (value < config.getLowLimit()) return theme.getWarningColor(); return theme.getNormalColor(); } } // Historical trend visualizer public class HistoricalTrendVisualizer extends AbstractVisualizer { private TimeRange timeRange; private TrendStyle style; public HistoricalTrendVisualizer(HistoricalData data, DisplayConfig config, TimeRange range) { super(data, config); this.timeRange = range; this.style = TrendStyle.LINE; } @Override protected void renderData(DisplayCanvas canvas) { HistoricalData histData = (HistoricalData) data; List points = histData.getPointsInRange(timeRange); if (points.isEmpty()) { renderNoDataMessage(canvas); return; } // Historical trend specific rendering switch (style) { case LINE: renderLineChart(canvas, points); break; case BAR: renderBarChart(canvas, points); break; case AREA: renderAreaChart(canvas, points); break; } renderTimeAxis(canvas, points); renderValueAxis(canvas, points); } @Override protected void onDataUpdated() { // Historical data specific update handling updateStatistics(); recalculateScaling(); } @Override public List getSupportedInteractions() { return Arrays.asList( DisplayInteraction.PAN, DisplayInteraction.ZOOM, DisplayInteraction.CHANGE_TIMERANGE, DisplayInteraction.EXPORT_DATA ); } // Historical trend specific methods private void renderLineChart(DisplayCanvas canvas, List points) { canvas.setColor(theme.getDataColor()); canvas.setStroke(theme.getLineStroke()); for (int i = 1; i < points.size(); i++) { Point p1 = transformToCanvas(points.get(i-1), canvas); Point p2 = transformToCanvas(points.get(i), canvas); canvas.drawLine(p1.x, p1.y, p2.x, p2.y); } } } // Single-line diagram visualizer public class SingleLineDiagramVisualizer extends AbstractVisualizer { private GridTopology topology; private Map componentVisuals; public SingleLineDiagramVisualizer(TopologyData data, DisplayConfig config) { super(data, config); this.topology = ((TopologyData) data).getTopology(); initializeComponentVisuals(); } @Override protected void renderData(DisplayCanvas canvas) { // Single-line diagram specific rendering renderGridBackground(canvas); // Render buses for (Bus bus : topology.getBuses()) { renderBus(canvas, bus); } // Render transmission lines for (TransmissionLine line : topology.getLines()) { renderTransmissionLine(canvas, line); } // Render transformers for (Transformer xfmr : topology.getTransformers()) { renderTransformer(canvas, xfmr); } // Render circuit breakers for (CircuitBreaker breaker : topology.getBreakers()) { renderCircuitBreaker(canvas, breaker); } // Render generators for (Generator gen : topology.getGenerators()) { renderGenerator(canvas, gen); } // Render loads for (Load load : topology.getLoads()) { renderLoad(canvas, load); } } @Override protected void onDataUpdated() { // Topology update handling updateTopology(((TopologyData) data).getTopology()); updateComponentStates(); } @Override public List getSupportedInteractions() { return Arrays.asList( DisplayInteraction.PAN, DisplayInteraction.ZOOM, DisplayInteraction.SELECT_COMPONENT, DisplayInteraction.SHOW_DETAILS ); } // Single-line diagram specific methods private void renderBus(DisplayCanvas canvas, Bus bus) { ComponentVisual visual = componentVisuals.get(bus.getId()); Point position = visual.getPosition(); canvas.setColor(getBusColor(bus)); canvas.fillCircle(position.x, position.y, visual.getRadius()); if (bus.hasVoltageMeasurement()) { renderVoltageLabel(canvas, position, bus.getVoltage()); } } } // Visualization manager using polymorphism public class VisualizationManager { private Map visualizers = new HashMap<>(); private DisplayCanvas mainCanvas; public void addVisualizer(String viewId, DataVisualizer visualizer) { visualizers.put(viewId, visualizer); } public void renderAll() { // Polymorphic rendering - different behavior for each visualizer type for (DataVisualizer visualizer : visualizers.values()) { visualizer.render(mainCanvas.getSubCanvas(visualizer.getPreferredSize())); } } public void updateData(String viewId, VisualizationData newData) { DataVisualizer visualizer = visualizers.get(viewId); if (visualizer != null) { visualizer.updateData(newData); // Polymorphic update } } public void handleInteraction(String viewId, DisplayInteraction interaction, Point location) { DataVisualizer visualizer = visualizers.get(viewId); if (visualizer != null && visualizer.getSupportedInteractions().contains(interaction)) { // Dispatch to appropriate interaction handler // Each visualizer handles interactions differently } } }

This polymorphic visualization system:

  • Eliminates complex conditional logic for different visualization types
  • Allows new visualization types to be added without modifying existing render logic
  • Provides consistent interface for rendering, updating, and interacting with visualizations
  • Enables visualizers to have type-specific interactions and behaviors
  • Supports the Open/Closed Principle for visualization extensions
9

Combining OOP Pillars for Event Processing

Design an event processing system for a SCADA system that handles different types of events (data changes, alarms, operator actions, system events) using all four OOP pillars. Show how abstraction, encapsulation, inheritance, and polymorphism work together in your design.

Model Answer

// ========== ABSTRACTION ========== // Abstract base event class public abstract class SCADAEvent { protected final String eventId; protected final LocalDateTime timestamp; protected final EventSeverity severity; protected final String source; public SCADAEvent(String source, EventSeverity severity) { this.eventId = UUID.randomUUID().toString(); this.timestamp = LocalDateTime.now(); this.severity = severity; this.source = source; } // Abstract methods defining the event interface public abstract EventType getType(); public abstract String getDescription(); public abstract Map getEventData(); // Common functionality for all events public boolean requiresImmediateProcessing() { return severity == EventSeverity.CRITICAL || severity == EventSeverity.HIGH; } public String getEventSummary() { return String.format("[%s] %s: %s", timestamp, source, getDescription()); } } // ========== ENCAPSULATION ========== // Event data is encapsulated within each event type public class DataChangeEvent extends SCADAEvent { private final String dataPointId; private final Object oldValue; private final Object newValue; private final DataQuality quality; // Private constructor - use factory method private DataChangeEvent(String source, String dataPointId, Object oldValue, Object newValue, DataQuality quality) { super(source, calculateSeverity(oldValue, newValue, quality)); this.dataPointId = dataPointId; this.oldValue = oldValue; this.newValue = newValue; this.quality = quality; } // Factory method encapsulates construction logic public static DataChangeEvent create(String source, String dataPointId, Object oldValue, Object newValue, DataQuality quality) { validateDataPoint(dataPointId); validateValues(oldValue, newValue); return new DataChangeEvent(source, dataPointId, oldValue, newValue, quality); } // Encapsulated severity calculation private static EventSeverity calculateSeverity(Object oldValue, Object newValue, DataQuality quality) { if (quality == DataQuality.BAD) return EventSeverity.HIGH; if (isSignificantChange(oldValue, newValue)) return EventSeverity.MEDIUM; return EventSeverity.LOW; } // Getters provide controlled access to encapsulated data @Override public Map getEventData() { Map data = new HashMap<>(); data.put("dataPointId", dataPointId); data.put("oldValue", oldValue); data.put("newValue", newValue); data.put("quality", quality.toString()); data.put("changePercentage", calculateChangePercentage(oldValue, newValue)); return data; } @Override public String getDescription() { return String.format("Data point %s changed from %s to %s", dataPointId, oldValue, newValue); } @Override public EventType getType() { return EventType.DATA_CHANGE; } // Private helper methods - implementation details hidden private static boolean isSignificantChange(Object oldValue, Object newValue) { // Complex significance calculation encapsulated here if (oldValue instanceof Number && newValue instanceof Number) { double oldNum = ((Number) oldValue).doubleValue(); double newNum = ((Number) newValue).doubleValue(); return Math.abs((newNum - oldNum) / oldNum) > 0.05; // 5% change } return !Objects.equals(oldValue, newValue); } } // ========== INHERITANCE ========== // Hierarchy of alarm events public abstract class AlarmEvent extends SCADAEvent { protected final String alarmId; protected final AlarmType alarmType; protected final LocalDateTime acknowledgeTime; protected final String acknowledgedBy; public AlarmEvent(String source, String alarmId, AlarmType type, EventSeverity severity) { super(source, severity); this.alarmId = alarmId; this.alarmType = type; this.acknowledgeTime = null; this.acknowledgedBy = null; } @Override public EventType getType() { return EventType.ALARM; } // Common alarm functionality public boolean isAcknowledged() { return acknowledgeTime != null; } public abstract String getAlarmMessage(); public abstract List getRecommendedActions(); } // Inherited specialized alarm types public class EquipmentAlarmEvent extends AlarmEvent { private final EquipmentType equipmentType; private final String equipmentId; private final EquipmentFailure failureMode; public EquipmentAlarmEvent(String source, String alarmId, EquipmentType equipmentType, String equipmentId, EquipmentFailure failureMode) { super(source, alarmId, AlarmType.EQUIPMENT, calculateSeverityFromFailure(failureMode)); this.equipmentType = equipmentType; this.equipmentId = equipmentId; this.failureMode = failureMode; } @Override public String getDescription() { return String.format("%s %s has failure: %s", equipmentType, equipmentId, failureMode); } @Override public String getAlarmMessage() { return String.format("EQUIPMENT ALARM: %s %s - %s", equipmentType, equipmentId, failureMode.getDescription()); } @Override public List getRecommendedActions() { return failureMode.getRecommendedActions(); } @Override public Map getEventData() { Map data = super.getEventData(); data.put("equipmentType", equipmentType); data.put("equipmentId", equipmentId); data.put("failureMode", failureMode); data.put("requiresMaintenance", failureMode.requiresImmediateMaintenance()); return data; } private static EventSeverity calculateSeverityFromFailure(EquipmentFailure failure) { switch (failure.getCriticality()) { case CRITICAL: return EventSeverity.CRITICAL; case HIGH: return EventSeverity.HIGH; case MEDIUM: return EventSeverity.MEDIUM; default: return EventSeverity.LOW; } } } public class CommunicationAlarmEvent extends AlarmEvent { private final String deviceAddress; private final CommunicationProtocol protocol; private final Duration outageDuration; public CommunicationAlarmEvent(String source, String alarmId, String deviceAddress, CommunicationProtocol protocol, Duration outageDuration) { super(source, alarmId, AlarmType.COMMUNICATION, calculateSeverityFromOutage(outageDuration)); this.deviceAddress = deviceAddress; this.protocol = protocol; this.outageDuration = outageDuration; } // Overridden methods with communication-specific implementations @Override public String getDescription() { return String.format("Communication loss with %s (%s) for %d minutes", deviceAddress, protocol, outageDuration.toMinutes()); } // Inherits common alarm behavior but provides specific implementation } // ========== POLYMORPHISM ========== // Event processor using polymorphism public class EventProcessor { private List handlers = new ArrayList<>(); private EventQueue eventQueue = new PriorityEventQueue(); // Polymorphic event processing public void processEvent(SCADAEvent event) { // Single interface, multiple implementations logEvent(event); // Different processing based on event type (polymorphism) if (event.requiresImmediateProcessing()) { processImmediately(event); } else { eventQueue.add(event); } // Notify all handlers - each handles events differently for (EventHandler handler : handlers) { // Polymorphic call - each handler decides how to handle the event handler.handleEvent(event); } // Update statistics based on event type updateStatistics(event.getType(), event.getSeverity()); } // Polymorphic logging private void logEvent(SCADAEvent event) { // Different log formats based on event type String logEntry = event.getEventSummary(); EventLogger.log(logEntry, event.getSeverity(), event.getEventData()); } // Polymorphic immediate processing private void processImmediately(SCADAEvent event) { switch (event.getType()) { case ALARM: handleAlarmImmediately((AlarmEvent) event); // Polymorphic break; case DATA_CHANGE: handleDataChangeImmediately((DataChangeEvent) event); // Polymorphic break; case OPERATOR_ACTION: handleOperatorActionImmediately((OperatorActionEvent) event); // Polymorphic break; case SYSTEM_EVENT: handleSystemEventImmediately((SystemEvent) event); // Polymorphic break; } } } // Polymorphic event handlers public interface EventHandler { void handleEvent(SCADAEvent event); } public class AlarmNotificationHandler implements EventHandler { @Override public void handleEvent(SCADAEvent event) { // Only handles alarm events if (event.getType() == EventType.ALARM) { AlarmEvent alarm = (AlarmEvent) event; sendNotification(alarm.getAlarmMessage(), alarm.getSeverity()); // Different notification methods based on alarm type if (alarm instanceof EquipmentAlarmEvent) { notifyMaintenanceTeam((EquipmentAlarmEvent) alarm); } else if (alarm instanceof CommunicationAlarmEvent) { notifyCommunicationTeam((CommunicationAlarmEvent) alarm); } } } } public class DatabaseStorageHandler implements EventHandler { @Override public void handleEvent(SCADAEvent event) { // Stores all events, but in different tables/structures String tableName = getTableForEventType(event.getType()); Map data = event.getEventData(); // Each event type provides different data through getEventData() database.insert(tableName, data); // Additional processing based on event type if (event instanceof DataChangeEvent) { updateTrendData((DataChangeEvent) event); } } } public class RealTimeUpdateHandler implements EventHandler { @Override public void handleEvent(SCADAEvent event) { // Updates different parts of UI based on event type switch (event.getType()) { case DATA_CHANGE: updateDataDisplay((DataChangeEvent) event); break; case ALARM: updateAlarmDisplay((AlarmEvent) event); break; case OPERATOR_ACTION: updateOperatorLog((OperatorActionEvent) event); break; } } } // Event factory using polymorphism public class EventFactory { public static SCADAEvent createDataChange(String source, String dataPointId, Object oldValue, Object newValue) { return DataChangeEvent.create(source, dataPointId, oldValue, newValue, DataQuality.GOOD); } public static SCADAEvent createEquipmentAlarm(String source, String equipmentId, EquipmentType type, EquipmentFailure failure) { return new EquipmentAlarmEvent(source, generateAlarmId(), type, equipmentId, failure); } // More factory methods... // Client code uses the factory, dealing only with SCADAEvent abstraction public void processValueChange(String dataPointId, Object newValue) { Object oldValue = getCurrentValue(dataPointId); SCADAEvent event = EventFactory.createDataChange("RTU1", dataPointId, oldValue, newValue); eventProcessor.processEvent(event); // Polymorphic processing } }

This design integrates all four OOP pillars:

  • Abstraction: SCADAEvent defines the essential interface for all events
  • Encapsulation: Each event type encapsulates its specific data and construction logic
  • Inheritance: Specialized event types inherit common functionality from base classes
  • Polymorphism: Event processors and handlers work with the abstract SCADAEvent interface, with different behavior for each concrete event type

Together, these pillars create a flexible, maintainable event processing system that can easily accommodate new event types and processing requirements.

10

Integrated SCADA Component Design

Design a complete Transformer class for a SCADA system that demonstrates all four OOP pillars working together. Your design should include monitoring, control, protection, and diagnostics functionality.

Model Answer

// ========== ABSTRACTION ========== // Abstract base for all power equipment public abstract class PowerEquipment { protected final String equipmentId; protected final EquipmentType type; protected GeoLocation location; protected OperationalStatus status; public PowerEquipment(String id, EquipmentType type, GeoLocation location) { this.equipmentId = id; this.type = type; this.location = location; this.status = OperationalStatus.IN_SERVICE; } // Abstract methods defining core equipment behavior public abstract Measurement getMeasurement(MeasurementType type); public abstract boolean executeControl(ControlCommand command); public abstract List checkAlarms(); public abstract EquipmentHealth getHealthStatus(); // Common concrete methods public String getEquipmentInfo() { return String.format("%s [%s] at %s - Status: %s", type, equipmentId, location, status); } public boolean isInService() { return status == OperationalStatus.IN_SERVICE; } } // ========== ENCAPSULATION ========== public class Transformer extends PowerEquipment { // Encapsulated internal state private TransformerCore core; private TapChanger tapChanger; private CoolingSystem cooling; private Bushings bushings; private ProtectionRelay protection; // Encapsulated operational parameters private double ratedPowerKVA; private VoltageRatio voltageRatio; private VectorGroup vectorGroup; private double temperature; private double loadPercentage; // Encapsulated diagnostic data private DissolvedGasAnalysis dgaData; private FrequencyResponseAnalysis fraData; private List maintenanceHistory; // Private constructor - use factory method private Transformer(String id, TransformerSpec spec, GeoLocation location) { super(id, EquipmentType.TRANSFORMER, location); initializeFromSpec(spec); } // Factory method encapsulates complex construction public static Transformer create(String id, TransformerSpec spec, GeoLocation location) { validateSpec(spec); Transformer transformer = new Transformer(id, spec, location); transformer.initializeComponents(); transformer.performPreCommissioningTests(); return transformer; } // Encapsulated initialization private void initializeFromSpec(TransformerSpec spec) { this.ratedPowerKVA = spec.getRatedPowerKVA(); this.voltageRatio = spec.getVoltageRatio(); this.vectorGroup = spec.getVectorGroup(); // Initialize components with encapsulated configurations this.core = new TransformerCore(spec.getCoreType(), spec.getCoreLosses()); this.tapChanger = new TapChanger(spec.getTapChangerType(), spec.getTapRange()); this.cooling = new CoolingSystem(spec.getCoolingType()); this.bushings = new Bushings(spec.getBushingSpecs()); this.protection = new TransformerDifferentialRelay(id + "_PROT"); this.dgaData = new DissolvedGasAnalysis(); this.fraData = new FrequencyResponseAnalysis(); this.maintenanceHistory = new ArrayList<>(); } // ========== PUBLIC INTERFACE (Encapsulation) ========== @Override public Measurement getMeasurement(MeasurementType type) { // Encapsulated measurement logic switch (type) { case WINDING_TEMPERATURE: return getWindingTemperature(); case OIL_TEMPERATURE: return getOilTemperature(); case LOAD_CURRENT: return getLoadCurrent(); case VOLTAGE_RATIO: return getVoltageRatioMeasurement(); case TAP_POSITION: return getTapPosition(); default: throw new IllegalArgumentException("Unsupported measurement type for transformer"); } } @Override public boolean executeControl(ControlCommand command) { // Encapsulated command validation and execution if (!isInService()) { throw new IllegalStateException("Transformer is not in service"); } if (!validateCommand(command)) { logInvalidCommand(command); return false; } // Execute based on command type boolean success = executeCommandInternal(command); if (success) { logCommandExecution(command); notifySCADA(command); } return success; } @Override public List checkAlarms() { List alarms = new ArrayList<>(); // Check each subsystem for alarms (encapsulated) alarms.addAll(checkTemperatureAlarms()); alarms.addAll(checkLoadAlarms()); alarms.addAll(checkOilAlarms()); alarms.addAll(checkBushingAlarms()); alarms.addAll(checkProtectionAlarms()); alarms.addAll(checkDiagnosticAlarms()); return alarms; } @Override public EquipmentHealth getHealthStatus() { // Encapsulated health assessment algorithm HealthScore score = calculateHealthScore(); return new EquipmentHealth(equipmentId, score, getHealthIndicators()); } // ========== ENCAPSULATED IMPLEMENTATION DETAILS ========== private Measurement getWindingTemperature() { // Complex temperature measurement with RTD calibration double rawTemp = readWindingRTD(); double corrected = applyRTDCorrection(rawTemp); double compensated = compensateForLoad(corrected, loadPercentage); return new Measurement( MeasurementType.WINDING_TEMPERATURE, compensated, LocalDateTime.now(), "°C", calculateTemperatureQuality(corrected) ); } private boolean executeCommandInternal(ControlCommand command) { // Encapsulated command execution logic if (command instanceof TapChangeCommand) { return executeTapChange((TapChangeCommand) command); } else if (command instanceof CoolingControlCommand) { return executeCoolingControl((CoolingControlCommand) command); } else if (command instanceof ProtectionSettingCommand) { return executeProtectionSetting((ProtectionSettingCommand) command); } return false; } private boolean executeTapChange(TapChangeCommand command) { // Complex tap change logic encapsulated if (!tapChanger.isTapChangePermitted()) { return false; } synchronized (tapChanger) { performPreTapChangeChecks(); boolean success = tapChanger.changeTap(command.getTargetPosition()); if (success) { performPostTapChangeActions(); updateVoltageRatio(); } return success; } } // ========== INHERITANCE ========== // Transformer inherits from PowerEquipment and can be extended further public class AutoTransformer extends Transformer { private double commonWindingRatio; public AutoTransformer(String id, TransformerSpec spec, GeoLocation location, double commonWindingRatio) { super(id, spec, location); this.commonWindingRatio = commonWindingRatio; } @Override public Measurement getVoltageRatioMeasurement() { // Override with auto-transformer specific calculation double ratio = calculateAutoTransformerRatio(); return new Measurement(MeasurementType.VOLTAGE_RATIO, ratio, LocalDateTime.now()); } // Additional auto-transformer specific methods public double getCommonWindingCurrent() { return calculateCommonWindingCurrent(getLoadCurrent().getValue()); } } // Specialized transformer types public class PhaseShiftingTransformer extends Transformer { private double phaseShiftAngle; public PhaseShiftingTransformer(String id, TransformerSpec spec, GeoLocation location, double maxPhaseShift) { super(id, spec, location); this.phaseShiftAngle = 0; } @Override public boolean executeControl(ControlCommand command) { if (command instanceof PhaseShiftCommand) { return executePhaseShift((PhaseShiftCommand) command); } return super.executeControl(command); } private boolean executePhaseShift(PhaseShiftCommand command) { // Phase shifting transformer specific implementation return setPhaseShiftAngle(command.getAngle()); } } // ========== POLYMORPHISM ========== // Transformer can be used polymorphically through PowerEquipment interface public class Substation { private List equipment = new ArrayList<>(); public void addEquipment(PowerEquipment equipment) { this.equipment.add(equipment); } public List getAllMeasurements() { List measurements = new ArrayList<>(); // Polymorphic call - different behavior for each equipment type for (PowerEquipment eq : equipment) { // Each equipment type provides measurements differently measurements.addAll(getEquipmentMeasurements(eq)); } return measurements; } public List checkAllAlarms() { List allAlarms = new ArrayList<>(); // Polymorphic alarm checking for (PowerEquipment eq : equipment) { // Each equipment type checks alarms differently allAlarms.addAll(eq.checkAlarms()); } return allAlarms; } public void executeControlOnAll(ControlCommand command) { // Polymorphic command execution for (PowerEquipment eq : equipment) { if (eq.supportsCommand(command)) { // Each equipment type executes commands differently eq.executeControl(command); } } } } // Transformer diagnostics using polymorphism public interface DiagnosticTest { DiagnosticResult runTest(Transformer transformer); String getTestName(); Duration getEstimatedDuration(); } // Different diagnostic tests for transformers public class TurnRatioTest implements DiagnosticTest { @Override public DiagnosticResult runTest(Transformer transformer) { // Polymorphic - works with any transformer Map ratios = transformer.measureAllTapRatios(); return new TurnRatioResult(ratios, transformer.getRatedRatio()); } @Override public String getTestName() { return "Turn Ratio Test"; } } public class InsulationResistanceTest implements DiagnosticTest { @Override public DiagnosticResult runTest(Transformer transformer) { // Different implementation for insulation test Map resistances = transformer.measureInsulationResistance(); return new InsulationResult(resistances); } } // Transformer diagnostics manager public class DiagnosticManager { private List scheduledTests = new ArrayList<>(); public void runAllTests(Transformer transformer) { List results = new ArrayList<>(); // Polymorphic test execution for (DiagnosticTest test : scheduledTests) { // Each test runs differently on the transformer DiagnosticResult result = test.runTest(transformer); results.add(result); // Different handling based on result type processTestResult(result); } generateDiagnosticReport(results); } private void processTestResult(DiagnosticResult result) { // Polymorphic result processing if (result instanceof TurnRatioResult) { processTurnRatioResult((TurnRatioResult) result); } else if (result instanceof InsulationResult) { processInsulationResult((InsulationResult) result); } else if (result instanceof DGAResult) { processDGAResult((DGAResult) result); } } } // ========== INTEGRATED USAGE ========== public class SCADATransformerController { private Map transformers = new HashMap<>(); private AlarmProcessor alarmProcessor; private DataLogger dataLogger; private ControlValidator controlValidator; public void monitorAllTransformers() { for (Transformer transformer : transformers.values()) { // ABSTRACTION: Using PowerEquipment interface PowerEquipment equipment = transformer; // Collect measurements (ENCAPSULATION: internal details hidden) List measurements = collectTransformerMeasurements(transformer); // Check alarms (POLYMORPHISM: transformer-specific alarm checking) List alarms = equipment.checkAlarms(); // Process alarms for (Alarm alarm : alarms) { alarmProcessor.process(alarm); // Polymorphic alarm processing } // Log data dataLogger.logMeasurements(equipment.getEquipmentId(), measurements); // Update health status (ENCAPSULATION: complex health algorithm) EquipmentHealth health = equipment.getHealthStatus(); updateHealthDisplay(health); } } public boolean controlTransformer(String transformerId, ControlCommand command) { Transformer transformer = transformers.get(transformerId); if (transformer == null) { return false; } // INHERITANCE: Transformer may be specialized type (AutoTransformer, etc.) // POLYMORPHISM: executeControl works for all transformer types return transformer.executeControl(command); } public void performDiagnostics(String transformerId, DiagnosticTest test) { Transformer transformer = transformers.get(transformerId); // POLYMORPHISM: Test runs differently based on concrete test type DiagnosticResult result = test.runTest(transformer); // Process result based on its type (more polymorphism) processDiagnosticResult(result); } } }

This comprehensive Transformer design demonstrates all four OOP pillars working in harmony:

  1. Abstraction: The PowerEquipment abstract class defines the essential interface for all power equipment, including transformers.
  2. Encapsulation: The Transformer class encapsulates complex internal state, measurement logic, control execution, and diagnostic algorithms behind a clean public interface.
  3. Inheritance: Transformer inherits from PowerEquipment, and specialized transformers (AutoTransformer, PhaseShiftingTransformer) inherit from Transformer, reusing and extending functionality.
  4. Polymorphism: The SCADA system can treat all transformers (and all power equipment) uniformly through the PowerEquipment interface, with specialized behavior provided by each concrete class.

This integrated approach creates a maintainable, extensible, and robust SCADA component that can evolve with changing requirements while maintaining a clean separation of concerns.

Key Takeaways: The four pillars of OOP work synergistically in SCADA system design. Abstraction defines what components do, encapsulation hides how they do it internally, inheritance enables code reuse and specialization, and polymorphism allows components to be treated uniformly while exhibiting type-specific behavior. Together, they create systems that are modular, maintainable, and extensible - essential qualities for critical infrastructure like power grids.