How's this code for task system with custom "instances"

These modules are for a task system, where “Tasks” are objects that hold data such as associated players and child objectives, while “Objectives” are objects that manage actually progressing through a task.

Task.lua (Class)

local Task = {}
local Signal = require(game:GetService("ReplicatedStorage").Utils.Signal)

function Task.new(callback: (any) -> (any)?)
	local self = {}

	-- Properties
	self.Name = "Task"
	self.IsCompleted = false
	self._Callback = callback or nil
	self._Objectives = {}
	self.Players = {}
	
	-- Events
	self.Changed = Signal.new()
	self.Completed = Signal.new()
	
	local proxy = setmetatable({}, {
		__index = function(_, key)
			return self[key] or Task[key]
		end,
		__newindex = function(_, key, value)
			if self[key] ~= value then
				self[key] = value
				self.Changed:Fire(key, value)
			end
		end
	})

	return proxy
end

function Task:GetName(): string
	return self.Name
end

function Task:AddPlayer(player: Player)
	table.insert(self.Players, player)
end

function Task:RemovePlayer(player: Player)
	for i, v in pairs(self.Players) do
		if v == player then
			table.remove(self.Players, i)
		end
	end
end

function Task:GetObjectives(): {any}
	return self._Objectives
end

function Task:ClearAllObjectives()
	for _, objective in pairs(self._Objectives) do
		objective:Destroy()
	end
end

function Task:Complete(priorityPlayer: Player?)
	assert(not self.IsCompleted, "This task is already completed")
	self.IsCompleted = true
	self.Completed:Fire(self:GetPlayers(), priorityPlayer)
	if self._Callback then
		self._Callback(self:GetPlayers(), priorityPlayer)
	end
end

function Task:Destroy()
	self:ClearAllObjectives()
	self.Changed:Destroy()
	self.Completed:Destroy()
	self = nil
end

return Task

Objective.lua (DataType)

local Objective = {}
Objective.__index = Objective

type ObjectiveType = 
"CollectItems"

function Objective.new(objectiveType: ObjectiveType, ...)
	assert(script:FindFirstChild(objectiveType), "Could not find objective of that type")
	return require(script:FindFirstChild(objectiveType)).new(...)
end

return Objective

BaseObjective.lua (Class)

local Objective = {}
local Signal = require(game:GetService("ReplicatedStorage").Utils.Signal)

function Objective.new(callback: (any) -> any?)
	local self = {}

	-- Properties
	self.Name = "Objective"
	self.Text = "Complete This Objective"
	self.IsCompleted = false
	self._Callback = callback or nil
	self.Parent = nil
	
	-- Events
	self.Changed = Signal.new()
	self.AncestryChanged = Signal.new()
	self.Completed = Signal.new()

	local proxy = setmetatable({}, {
		__index = function(_, key)
			return self[key] or Objective[key]
		end,
		__newindex = function(_, key, value)
			if self[key] ~= value then
				self[key] = value
				self.Changed:Fire(key, value)
				if tostring(key) == "Parent" then
					self.AncestryChanged:Fire(value)
					self.Parent[self.Name] = self
				end
			end
		end
	})

	return proxy
end

function Objective:GetText(): string
	return self.Text
end

function Objective:IsDescendantOf(task: {any}): boolean
	return self.Parent == task
end

function Objective:Complete(priorityPlayer: Player?)
	assert(self.IsCompleted, "This objective is already completed")
	self.IsCompleted = true
	self.Completed:Fire(self.Parent and self.Parent:GetPlayers() or {}, priorityPlayer)
	if self._Callback then
		self._Callback(self.Parent and self.Parent:GetPlayers() or {}, priorityPlayer)
	end
end

function Objective:Destroy()
	self.Changed:Destroy()
	self.AncestryChanged:Destroy()
	self.Completed:Destroy()
	self = nil
end

return Objective
local taskCallback = function() print("Completed task!") end
local newTask = Task.new(taskCallback)
newTask.Name = "Task"
newTask:AddPlayer(game.Players.Galicate)

local objectiveCallback = function() print("Completed objective!") end
local newObjective = Objective.new("CollectItems", objectiveCallback)
newObjective.Items = {"Item1", "Item2", "Item3"}
newObjective.Text = "Collect 3 items"
newObjective.Parent = newTask