I learned a lot about Object Oriented Programming from this blog article from 2020 about “Gang of Four” design patterns. I should say that I learned about OOP long ago via a Java book but as a data professional I have never needed to know much about these things. I should emphasize that OOP never clicked with me, and when I discovered Julia it felt like a natural language.
How would we properly write idiomatic (non-OOP) Julia version of the solution? The python for the blog author’s preferred solution is pasted far below.
Here is my attempt in Julia to respond to the author’s preferred solution to a toy implementation of Logger in python, using the “Composition over Inheritance” principle from “Design Patterns: Elements of Reusable Object-Oriented Software”. Their solution avoids the explosion of subclasses problem that can happen with inheritance in OOP.
function log_message(message::String, sink::T) where {T <: AbstractPath}
io = open(sink, "a")
write(io, message * "\n")
close(io)
end
function log_message(message::String, sink::TCPSocket)
send(socket::UDPSocket, message)
end
function log_message(message::String, sink::SysLog)
write(sink, message)
end
function message_filter(pattern::Vector{String}, message::String)
for x in pattern
if occursin(x, message)
return message
end
end
end
# TEST: log_message function
using FilePathsBase
msg1 = "Error: Its a bad bad problem here"
msg2 = "Warn: eh ok fine"
file = Path("./test-logger.txt")
log_message(message_filter(["Error"], msg1), file)
# or pipe the filtered message
message_filter(["Warn"], msg2) |> (x -> log_message(x, file))
shell> cat test-logger.txt
# Error: Its a bad bad problem here
# Warn: eh ok fine
From “Solution #4: Beyond the Gang of Four patterns”, the python from the post is copied below.
This is the blog author’s favored solution in python using Composition instead of Inheritance.
class Logger:
def __init__(self, filters, handlers):
self.filters = filters
self.handlers = handlers
def log(self, message):
if all(f.match(message) for f in self.filters):
for h in self.handlers:
h.emit(message)
# Filters now know only about strings!
class TextFilter:
def __init__(self, pattern):
self.pattern = pattern
def match(self, text):
return self.pattern in text
# Handlers look like “loggers” did in the previous solution.
class FileHandler:
def __init__(self, file):
self.file = file
def emit(self, message):
self.file.write(message + '\n')
self.file.flush()
class SocketHandler:
def __init__(self, sock):
self.sock = sock
def emit(self, message):
self.sock.sendall((message + '\n').encode('ascii'))
class SyslogHandler:
def __init__(self, priority):
self.priority = priority
def emit(self, message):
syslog.syslog(self.priority, message)
# results
f = TextFilter('Error')
h = FileHandler(sys.stdout)
logger = Logger([f], [h])
logger.log('Ignored: this will not be logged')
logger.log('Error: this is important')
# Error: this is important