Summary
Principle Name | Encapsulate What Varies |
Acronym | EWV |
Principle Type | Core Principle |
Purpose | Isolate part of the code that is likely to change later, by encapsulating them. |
Implementing Design Patterns | Factory Pattern State Pattern Strategy Pattern |
Related Principles | Open-Closed Principle(OCP) Separation of Concerns(SoC) Single Responsibility Principle(SRP) |
Definition
In software development requirement changes over time. And to adopt that we have to make changes to our code. Keeping that in mind we have to write our code, so that it becomes easier to adopt any change later.
This Encapsulate What Varies(EWV) principle is-
Isolate the part of the code that is likely to be changed later, by encapsulating them. So that the encapsulation works as a shield for the rest of the code.
As it is difficult to predict which part of the code might change, due to requirement change. We should decide, for which parts we want to have the ability to change with redesigning.
Explanation
Certain parts of the code are more likely to be changed. Say there are multiple options available for doing the same job. It is highly possible that more options will be added later.
Like, there can be multiple storage systems(like, local storage, Google drive, AWS S3, etc.), and multiple payment options(like, card, stripe, PayPal, etc.). So we should not implement the solution for only one option.
In these cases, we should use polymorphism, and use an interface(or common parent class), and implement the interface for all those options.
Let’s do it for the storage. Here the varying part is the storage systems, as any storage system can be used at any point of time. So we are encapsulating the storage implementation using polymorphism.
Storage Interface
Create an interface for all the storage systems, so that all can implement the same interface. We have declared a few methods according to our requirements.
// Storage.java
package com.bigboxcode;
public interface Storage {
int storeFile(String tempPath);
String retrieveFile(int fileId);
void printFileInfo(int fileId);
}
Java# Storage interface
from abc import ABC, abstractmethod
class Storage(ABC):
@abstractmethod
def store_file(self, temp_path: str) -> int:
pass
@abstractmethod
def retrieve_file(self, file_id: int) -> str:
pass
@abstractmethod
def print_file_info(self, file_id: int):
pass
Python// storage.go
package main
import "fmt"
// Storage interface defines the methods for storing and retrieving files
type Storage interface {
StoreFile(tempPath string) int
RetrieveFile(fileID int) string
PrintFileInfo(fileID int)
}
Go// Storage.php
<?php
interface Storage {
public function storeFile(string $tempPath): int;
public function retrieveFile(int $fileId): string;
public function printFileInfo(int $fileId): void;
}
PHP// Storage.ts
export interface Storage {
storeFile(tempPath: string): number;
retrieveFile(fileId: number): string;
printFileInfo(fileId: number): void;
}
TypeScriptLocal Storage Class
Create the LocalStorage class and implement the Storage interface. Here we have implemented the methods with some dummy data for demo purposes.
// LocalStorage.java
package com.bigboxcode;
import java.util.Random;
public class LocalStorage implements Storage {
@Override
public int storeFile(String tempPath) {
// Dummy random ID for demo purpose
return new Random().nextInt(100);
}
@Override
public String retrieveFile(int fileId) {
// Dummy URL for demo
return "https://bigboxcode.com/files/local/" + fileId;
}
@Override
public void printFileInfo(int fileId) {
System.out.println("Storage type: Local Storage");
System.out.println("File ID: " + fileId);
System.out.println("File URL: " + retrieveFile(fileId));
}
}
Java# Local Storage
import random
class LocalStorage(Storage):
def store_file(self, temp_path: str) -> int:
# Dummy random ID for demo purpose
return random.randint(1, 100)
def retrieve_file(self, file_id: int) -> str:
# Dummy url for demo
return f"https://bigboxcode.com/files/local/{file_id}"
def print_file_info(self, file_id: int):
print("Storage type: Local Storage")
print(f"File ID: {file_id}")
print(f"File URL: {self.retrieve_file(file_id)}")
Python// local_storage.go
package main
import (
"fmt"
"math/rand"
)
// LocalStorage is a concrete implementation of Storage for local storage
type LocalStorage struct{}
// StoreFile generates a random file ID for local storage
func (l *LocalStorage) StoreFile(tempPath string) int {
return rand.Intn(100)
}
// RetrieveFile returns a dummy URL for local storage
func (l *LocalStorage) RetrieveFile(fileID int) string {
return fmt.Sprintf("https://bigboxcode.com/files/local/%d", fileID)
}
// PrintFileInfo prints the file information for local storage
func (l *LocalStorage) PrintFileInfo(fileID int) {
fmt.Println("Storage type: Local Storage")
fmt.Printf("File ID: %d\n", fileID)
fmt.Printf("File URL: %s\n", l.RetrieveFile(fileID))
}
Go// LocalStorage.php
<?php
class LocalStorage implements Storage {
public function storeFile(string $tempPath): int {
// Dummy random ID for demo purpose
return rand(1, 100);
}
public function retrieveFile(int $fileId): string {
// Dummy URL for demo
return "https://bigboxcode.com/files/local/{$fileId}";
}
public function printFileInfo(int $fileId): void {
echo "Storage type: Local Storage\n";
echo "File ID: {$fileId}\n";
echo "File URL: " . $this->retrieveFile($fileId) . "\n";
}
}
PHP// LocalStorage.ts
import { Storage } from './Storage';
export class LocalStorage implements Storage {
storeFile(tempPath: string): number {
// Dummy random ID for demo purpose
return Math.floor(Math.random() * 100) + 1;
}
retrieveFile(fileId: number): string {
// Dummy URL for demo
return `https://bigboxcode.com/files/local/${fileId}`;
}
printFileInfo(fileId: number): void {
console.log("Storage type: Local Storage");
console.log(`File ID: ${fileId}`);
console.log(`File URL: ${this.retrieveFile(fileId)}`);
}
}
TypeScriptGoogle Drive Storage Class
Create the class GoogleDirveStorage and implement the Storage interface.
// GoogleDriveStorage.java
package com.bigboxcode;
import java.util.Random;
public class GoogleDriveStorage implements Storage {
@Override
public int storeFile(String tempPath) {
// Dummy random ID for demo purpose
return new Random().nextInt(1000);
}
@Override
public String retrieveFile(int fileId) {
// Dummy URL for demo
return "https://drive.google.com/file/d/1234_9K7654hu6RT_9j7JKY3fK/view";
}
@Override
public void printFileInfo(int fileId) {
System.out.println("Storage type: Google Drive");
System.out.println("File ID: " + fileId);
System.out.println("File URL: " + retrieveFile(fileId));
}
}
Java
class GoogleDriveStorage(Storage):
def store_file(self, temp_path: str) -> int:
# Dummy random ID for demo purpose
return random.randint(1, 1000)
def retrieve_file(self, file_id: int) -> str:
# Dummy url for demo
return "https://drive.google.com/file/d/1234_9K7654hu6RT_9j7JKY3fK/view"
def print_file_info(self, file_id: int):
print("Storage type: Google Drive")
print(f"File ID: {file_id}")
print(f"File URL: {self.retrieve_file(file_id)}")
Python// google_drive_storage.go
package main
import (
"fmt"
"math/rand"
)
// GoogleDriveStorage is a concrete implementation of Storage for Google Drive
type GoogleDriveStorage struct{}
// StoreFile generates a random file ID for Google Drive storage
func (g *GoogleDriveStorage) StoreFile(tempPath string) int {
return rand.Intn(1000)
}
// RetrieveFile returns a dummy URL for Google Drive
func (g *GoogleDriveStorage) RetrieveFile(fileID int) string {
return "https://drive.google.com/file/d/1234_9K7654hu6RT_9j7JKY3fK/view"
}
// PrintFileInfo prints the file information for Google Drive storage
func (g *GoogleDriveStorage) PrintFileInfo(fileID int) {
fmt.Println("Storage type: Google Drive")
fmt.Printf("File ID: %d\n", fileID)
fmt.Printf("File URL: %s\n", g.RetrieveFile(fileID))
}
Go// GoogleDriveStorage.php
<?php
class GoogleDriveStorage implements Storage {
public function storeFile(string $tempPath): int {
// Dummy random ID for demo purpose
return rand(1, 1000);
}
public function retrieveFile(int $fileId): string {
// Dummy URL for demo
return "https://drive.google.com/file/d/1234_9K7654hu6RT_9j7JKY3fK/view";
}
public function printFileInfo(int $fileId): void {
echo "Storage type: Google Drive\n";
echo "File ID: {$fileId}\n";
echo "File URL: " . $this->retrieveFile($fileId) . "\n";
}
}
PHP// GoogleDriveStorage.ts
import { Storage } from './Storage';
export class GoogleDriveStorage implements Storage {
storeFile(tempPath: string): number {
// Dummy random ID for demo purpose
return Math.floor(Math.random() * 1000) + 1;
}
retrieveFile(fileId: number): string {
// Dummy URL for demo
return "https://drive.google.com/file/d/1234_9K7654hu6RT_9j7JKY3fK/view";
}
printFileInfo(fileId: number): void {
console.log("Storage type: Google Drive");
console.log(`File ID: ${fileId}`);
console.log(`File URL: ${this.retrieveFile(fileId)}`);
}
}
TypeScriptS3 Storage Class
Create the class S3Storage and implement the Storage interface.
// S3Storage.java
package com.bigboxcode;
import java.util.Random;
public class S3Storage implements Storage {
@Override
public int storeFile(String tempPath) {
// Dummy random ID for demo purpose
return new Random().nextInt(10000);
}
@Override
public String retrieveFile(int fileId) {
// Dummy URL for demo
return "https://bigboxcode.s3.amazonaws.com/pdf/UC-0e7654338-5697-4f99-b33-d89h87g5gf4gwfg.pdf";
}
@Override
public void printFileInfo(int fileId) {
System.out.println("Storage type: AWS S3");
System.out.println("File ID: " + fileId);
System.out.println("File URL: " + retrieveFile(fileId));
}
}
Javaimport random
class S3Storage(Storage):
def store_file(self, temp_path: str) -> int:
# Dummy random ID for demo purpose
# In a real scenario, the file would be uploaded to
# AWS S3 and the ID from the database or S3 response would be returned
return random.randint(1, 10000)
def retrieve_file(self, file_id: int) -> str:
# Dummy url for demo
# This URL dynamically generated or retrieved from S3
return "https://bigboxcode.s3.amazonaws.com/pdf/UC-0e7654338-5697-4f99-b33-d89h87g5gf4gwfg.pdf"
def print_file_info(self, file_id: int):
print("Storage type: AWS S3")
print(f"File ID: {file_id}")
print(f"File URL: {self.retrieve_file(file_id)}")
Python// s3_storage.go
package main
import (
"fmt"
"math/rand"
)
// S3Storage is a concrete implementation of Storage for AWS S3
type S3Storage struct{}
// StoreFile generates a random file ID for S3 storage
func (s *S3Storage) StoreFile(tempPath string) int {
return rand.Intn(10000)
}
// RetrieveFile returns a dummy URL for S3 storage
func (s *S3Storage) RetrieveFile(fileID int) string {
return "https://bigboxcode.s3.amazonaws.com/pdf/UC-0e7654338-5697-4f99-b33-d89h87g5gf4gwfg.pdf"
}
// PrintFileInfo prints the file information for S3 storage
func (s *S3Storage) PrintFileInfo(fileID int) {
fmt.Println("Storage type: AWS S3")
fmt.Printf("File ID: %d\n", fileID)
fmt.Printf("File URL: %s\n", s.RetrieveFile(fileID))
}
Go// S3Storage.php
<?php
class S3Storage implements Storage {
public function storeFile(string $tempPath): int {
// Dummy random ID for demo purpose
return rand(1, 10000);
}
public function retrieveFile(int $fileId): string {
// Dummy URL for demo
return "https://bigboxcode.s3.amazonaws.com/pdf/UC-0e7654338-5697-4f99-b33-d89h87g5gf4gwfg.pdf";
}
public function printFileInfo(int $fileId): void {
echo "Storage type: AWS S3\n";
echo "File ID: {$fileId}\n";
echo "File URL: " . $this->retrieveFile($fileId) . "\n";
}
}
PHP// S3Storage.ts
import { Storage } from './Storage';
export class S3Storage implements Storage {
storeFile(tempPath: string): number {
// Dummy random ID for demo purpose
return Math.floor(Math.random() * 10000) + 1;
}
retrieveFile(fileId: number): string {
// Dummy URL for demo
return "https://bigboxcode.s3.amazonaws.com/pdf/UC-0e7654338-5697-4f99-b33-d89h87g5gf4gwfg.pdf";
}
printFileInfo(fileId: number): void {
console.log("Storage type: AWS S3");
console.log(`File ID: ${fileId}`);
console.log(`File URL: ${this.retrieveFile(fileId)}`);
}
}
TypeScriptStorage System Class
Create a class for managing storage systems, and performing operations. This class accepts an instance of the Storage interface, and then performs operations on that instance.
// StorageSystem.java
package com.bigboxcode;
public class StorageSystem {
private Storage storage;
public StorageSystem(Storage storage) {
this.storage = storage;
}
public int uploadFile(String tempPath) {
int fileId = storage.storeFile(tempPath);
storage.printFileInfo(fileId);
return fileId;
}
public String getFileUrl(int fileId) {
return storage.retrieveFile(fileId);
}
}
Javaclass StorageSystem:
def __init__(self, storage: Storage):
self.storage = storage
def upload_file(self, temp_path: str) -> int:
file_id = self.storage.store_file(temp_path)
self.storage.print_file_info(file_id)
return file_id
def get_file_url(self, file_id: int) -> str:
return self.storage.retrieve_file(file_id)
Python// storage_system.go
package main
// StorageSystem is a context that accepts different storage strategies
type StorageSystem struct {
storage Storage
}
// NewStorageSystem creates a new storage system with the given storage strategy
func NewStorageSystem(storage Storage) *StorageSystem {
return &StorageSystem{storage: storage}
}
// UploadFile stores the file using the provided storage strategy
func (ss *StorageSystem) UploadFile(tempPath string) int {
fileID := ss.storage.StoreFile(tempPath)
ss.storage.PrintFileInfo(fileID)
return fileID
}
// GetFileURL retrieves the file URL using the provided storage strategy
func (ss *StorageSystem) GetFileURL(fileID int) string {
return ss.storage.RetrieveFile(fileID)
}
Go// StorageSystem.php
<?php
class StorageSystem {
private Storage $storage;
public function __construct(Storage $storage) {
$this->storage = $storage;
}
public function uploadFile(string $tempPath): int {
$fileId = $this->storage->storeFile($tempPath);
$this->storage->printFileInfo($fileId);
return $fileId;
}
public function getFileUrl(int $fileId): string {
return $this->storage->retrieveFile($fileId);
}
}
PHP// StorageSystem.ts
import { Storage } from './Storage';
export class StorageSystem {
private storage: Storage;
constructor(storage: Storage) {
this.storage = storage;
}
uploadFile(tempPath: string): number {
const fileId = this.storage.storeFile(tempPath);
this.storage.printFileInfo(fileId);
return fileId;
}
getFileUrl(fileId: number): string {
return this.storage.retrieveFile(fileId);
}
}
TypeScriptUsage
We can create an instance of any of the storage classes(LocalStorage, GoogleDriveStorage, S3Storage), and pass that to StorageSystem. Then we can use the storage.
// Main.java
package com.bigboxcode;
public class Main {
public static void main(String[] args) {
// Usage example with LocalStorage
Storage localStorage = new LocalStorage();
StorageSystem storageSystem = new StorageSystem(localStorage);
int fileId = storageSystem.uploadFile("/path/to/local/file");
String url = storageSystem.getFileUrl(fileId);
System.out.println("Retrieved file URL: " + url);
// Usage example with GoogleDriveStorage
Storage googleDriveStorage = new GoogleDriveStorage();
storageSystem = new StorageSystem(googleDriveStorage);
fileId = storageSystem.uploadFile("/path/to/drive/file");
url = storageSystem.getFileUrl(fileId);
System.out.println("Retrieved file URL: " + url);
// Usage example with S3Storage
Storage s3Storage = new S3Storage();
storageSystem = new StorageSystem(s3Storage);
fileId = storageSystem.uploadFile("/path/to/s3/file");
url = storageSystem.getFileUrl(fileId);
System.out.println("Retrieved file URL: " + url);
}
}
Java# Usage example with LocalStorage
local_storage = LocalStorage()
storage_strategy = StorageSystem(local_storage)
file_id = storage_strategy.upload_file("/path/to/local/file")
url = storage_strategy.get_file_url(file_id)
print(f"Retrieved file URL: {url}")
# Usage example with GoogleDriveStorage
google_drive_storage = GoogleDriveStorage()
storage_strategy = StorageSystem(google_drive_storage)
file_id = storage_strategy.upload_file("/path/to/drive/file")
url = storage_strategy.get_file_url(file_id)
print(f"Retrieved file URL: {url}")
Python// main.go
package main
import "fmt"
func main() {
// Usage example with LocalStorage
localStorage := &LocalStorage{}
storageSystem := NewStorageSystem(localStorage)
fileID := storageSystem.UploadFile("/path/to/local/file")
url := storageSystem.GetFileURL(fileID)
fmt.Printf("Retrieved file URL: %s\n", url)
// Usage example with GoogleDriveStorage
googleDriveStorage := &GoogleDriveStorage{}
storageSystem = NewStorageSystem(googleDriveStorage)
fileID = storageSystem.UploadFile("/path/to/drive/file")
url = storageSystem.GetFileURL(fileID)
fmt.Printf("Retrieved file URL: %s\n", url)
}
Go// main.php
<?php
require_once 'Storage.php';
require_once 'LocalStorage.php';
require_once 'GoogleDriveStorage.php';
require_once 'S3Storage.php';
require_once 'StorageSystem.php';
// Usage example with LocalStorage
$localStorage = new LocalStorage();
$storageSystem = new StorageSystem($localStorage);
$fileId = $storageSystem->uploadFile("/path/to/local/file");
$url = $storageSystem->getFileUrl($fileId);
echo "Retrieved file URL: {$url}\n";
// Usage example with GoogleDriveStorage
$googleDriveStorage = new GoogleDriveStorage();
$storageSystem = new StorageSystem($googleDriveStorage);
$fileId = $storageSystem->uploadFile("/path/to/drive/file");
$url = $storageSystem->getFileUrl($fileId);
echo "Retrieved file URL: {$url}\n";
PHP// main.ts
import { LocalStorage } from './LocalStorage';
import { GoogleDriveStorage } from './GoogleDriveStorage';
import { S3Storage } from './S3Storage';
import { StorageSystem } from './StorageSystem';
// Usage example with LocalStorage
const localStorage = new LocalStorage();
const storageSystem = new StorageSystem(localStorage);
let fileId = storageSystem.uploadFile("/path/to/local/file");
let url = storageSystem.getFileUrl(fileId);
console.log(`Retrieved file URL: ${url}`);
// Usage example with GoogleDriveStorage
const googleDriveStorage = new GoogleDriveStorage();
const googleDriveStorageSystem = new StorageSystem(googleDriveStorage);
fileId = googleDriveStorageSystem.uploadFile("/path/to/drive/file");
url = googleDriveStorageSystem.getFileUrl(fileId);
console.log(`Retrieved file URL: ${url}`);
TypeScript