Three Ideas of Code Design

1. Combination instead of inheritance

1. Inheritance

Recently, Demo, a unity game of killing towers, was playing. Due to the limited level of individuals, the class inheritance level in the game was too deep, and the same attribute was held by many different parallel objects. As follows:

Then the problem is:
(1) Code modification is cumbersome: once an object in the inheritance chain is modified, it must not modify the related classes because of its "connecting" role (in violation of the open-close principle);
(2) Difficulty in data updating: If one layer of the inheritance chain needs external interaction and the interactive data is on another inheritance chain, then there will be a relationship line like the Liankang title in the elementary school test paper, and because it is an inheritance relationship, if two inheritance chains need to use the same data, Then you have to merge two chains (inherit or combine again) or have two data sources in your code;
(3) Poor readability: Sometimes when I want to find a piece of data in my code, I have to flip several files over and over for half a day (that's still the code I wrote).

2. Combination

The way to correct the problem with inheritance above is by combining.
Pull out all the functions of the above objects, and turn them into components by function, while the original objects become a master, a hook, and when our objects need which functions, mount which components, plug and play. In addition to solving the above problems, this can also benefit from the following:

(1) Separation of duties: a large inheritance class is separated into uncoupled components, and there is no need to buy the entire forest for a banana (inheritance tax).
(2) Interaction convenience: Because of the concept of "master control", when data interaction occurs between objects, I do not need to look up whether the inheritance node of the interactive object has this data, and master docking, just care if it has a component.

Interaction mode based on combinatorial thinking: external events are distributed by master calls, and data is transferred between components through master calls.

2. Status Substitution Mark

The fighter needs a way to represent his various gains and losses Buff, so what can he do?

1. Marking

The first way we do this is by "tagging"

class CWarrior(object):
	def __init__(self):
		self.m_BuffDict = {}	#Maintain a buff dictionary
		
	
	def QueryBuff(self, sBuffName):
		if sBuffName in self.m_BuffDict:
			return self.m_BuffDict[sBuffName]
		return 0
	
	def AddBuff(self, sBuffName, iValue):
		self.m_BuffDict[sBuffName] = iValue
	
	def RemoveBuff(self, sBuffName):
		if sBuffName in self.m_BuffDict:
			del self.m_BuffDict[sBuffName]

Preliminary looks good, but it's hard when your target needs to react to buff. For example, imagine the following scenario:

Fighting Objects A To Fight Objects B Released a skill based on B On the body buff Trigger various behaviors:
1,Bleeding ( Bleeding): 100 bonus points deducted from damage
2,Inductive ( Electric): 200 additional life points deducted when receiving lightning damage
3,Poisoning ( Poisoning): Extra deduction of 300 when injury is received * Poison Layer Points Life Value

So the function that gets the damage has to be written like this

def GetHurt(self, oSkill):
	if self.QueryBuff("Bleeding"):								#bleeding
		self.SubHP(100)
	if self.QueryBuff("Electric") and oSkill.Type() == "Thunder":	#Induction
		self.SubHP(200)
	if self.QueryBuff("Poisoning"):								#poisoning
		self.SubHP(300 * self.QueryBuff("Poisoning"))	
	......
	

In this way, without adding a buff tag, we have to write an additional if, and when buff effects are more complex, the function becomes ugly.

So we've introduced state objects to solve this problem.

2. Status

class CWarrior(object):
	def __init__(self):
		self.m_BuffDict = {}	#Maintain a buff dictionary
	
	def AddBuff(self, oBuff):		#oBuff Status Object
		self.m_BuffDict[oBuff.m_ID] = oBuff
	
	def RemoveBuff(self, iBuff):
		if iBuff in self.m_BuffDict:
			del self.m_BuffDict[iBuff]
	
	def GetHurt(self, oSkill):
		for oBuff in self.m_BuffDict.Values():
			oBuff.GetHurt(self, oSkill)
	

You can see that although a buff dictionary is also maintained, they differ from the GetHurt() method.

In the form of "state", we use state objects to describe the various states of the subject object, state data is placed in each state object, and each state realizes its own behavior response to events. The state object for the example above is implemented as follows:

#State Base Class
class CBaseBuff(object):
	def __init__(self):
		pass
	
	def GetHurt(self, oWarrior, oSkill):
		pass

#bleeding	
class CBleeding(CBaseBuff):
	def GetHurt(self, oWarrior, oSkill):
		oWarrior.SubHP(100)

#Induction
class CElectric(CBaseBuff):
	def GetHurt(self, oWarrior, oSkill):
		if oSkill.Type() == "Thunder":	#Induction
			oWarrior.SubHP(200)

#poisoning
class CPoisoning(CBaseBuff):
	def __init__(self):
		self.m_Value = 0

	def GetHurt(self, oWarrior, oSkill):
		oWarrior.SubHP(300 * self.m_Value)	
	

3. Process substitution for call

Write again tomorrow

Keywords: architecture

Added by philvia on Wed, 23 Feb 2022 19:12:43 +0200