Provider abstraction pattern
Overview
The Provider abstraction pattern hides the messy details of how a job is done. It gives you a clean, simple object (the Provider) to use, so you do not have to worry about the complicated steps inside. Imagine using an ATM. You just push a button to get cash. You do not know if the bank uses a slow computer or a fast one to check your balance. The ATM hides those details. It "provides" a simple way to get cash. In our code, sending an SMS can be complicated. We can hide it inside a Provider: ```python class FastSMSProvider: def send(self, phone, message): # Messy network code is hidden here return f"Sent '{message}' to {phone} quickly" class CheapSMSProvider: def send(self, phone, message): # Different messy code hidden here return f"Sent '{message}' to {phone} cheaply" ``` Our main code does not care which provider it uses. It just calls `.send()`: ```python def alert_user(provider): # We just trust the provider knows how to send it result = provider.send("9876543210", "Your OTP is 1234") print(result) my_provider = FastSMSProvider() alert_user(my_provider) ``` Let us practice. Here is a faded example. Fill in the blank to call the provider's standard method. ```python class DatabaseProvider: def save(self, data): print(f"Saving {data} to database") def backup_data(provider): # Ask the provider to save ____.save("User list") ``` The rule to remember: **A Provider hides complex work behind a simple, standard method.** There is a sharp edge to watch out for. What if `FastSMSProvider` needs a password to send, but `CheapSMSProvider` does not? If you put the password directly in the `send()` method, the methods will look different, and `alert_user` will break. Providers must always keep their public methods looking exactly the same.
It keeps your main application clean. If the SMS company changes how their system works, you only update the Provider class. The rest of your app stays untouched.
Where used: Sending emails or SMS, Saving files to different places (local disk vs cloud storage), Processing payments
Why learn this
- You will know how to write code that can easily switch between different services (like switching from AWS to Google Cloud).
- You will understand how large apps keep their main logic simple and easy to read.
Code walkthrough
class MockWeatherProvider:
def get_temp(self):
return 25
def display_weather(provider):
temp = provider.get_temp()
print(f"Temp is {temp}")
fake_api = MockWeatherProvider()
display_weather(fake_api)
Focus: temp = provider.get_temp()
Aha moment
class FastPrinter:
def print_doc(self, text): print("Fast:", text)
class SlowPrinter:
def print_doc(self, text, ink_level): print("Slow:", text)
def print_report(provider):
provider.print_doc("Annual Report")
print_report(FastPrinter())
# print_report(SlowPrinter()) # This would crash!
Prediction: If we uncomment the last line, what happens?
Common guess: It prints 'Slow: Annual Report'.
It crashes! The main code `print_report` only passes ONE argument. `SlowPrinter` demands two arguments. Providers must have the exact same method rules, or the abstraction fails.
Common mistakes
- Leaking details: If your provider returns a specific AWS error code instead of a generic error, the main code now has to know about AWS. The provider failed to hide the details.
- Different method names: If one provider uses `.send_sms()` and another uses `.deliver_text()`, the main code cannot easily swap between them. They must use the exact same method names.
Recall questions
- Explain the Provider abstraction pattern in your own words, as if to a classmate.
- Why is it important that all SMS providers use the exact same method name (like `.send()`)?
- What happens if one provider requires a password in its `.send()` method, but the other does not?
Understanding checks
What does this code print?
Saved to cloud
The `save_profile` function uses the `CloudStorageProvider`. It calls the `.store()` method on the provider, which returns the cloud message.
A developer writes an email system. The `GmailProvider` returns a `GoogleNetworkError` when it fails. Why is this a bad Provider abstraction?
Because the provider is leaking specific details about Google to the main code.
A good provider hides the messy details. It should catch the `GoogleNetworkError` and return a generic `EmailFailedError` so the main code does not need to know it is using Google.
Practice tasks
Standardize the provider
Given this code, fix the `StripeProvider` so that its method matches the `PayPalProvider`, allowing `process_cart` to use it without crashing.
Continue learning
Previous: Class Creation and Instantiation
Previous: Interface patterns in Python