Well,
my opinion (the way i do it) would be to combine both methods, but with emphasis on what @Kylotan said. Data-driven is typically better than using many different functions.
Since this is basically about objects you want to hold in your hand, you could create a parent-class 'HandEquippableObject', that holds Data shared between all such items, including bows, shields, 2handed and 1handed, special items and so on. Said data contains for example weight or slot-usage (for weight-based or slot-based inventory), a mesh, thumbnail, name, description, durability, preferred hand, etc....
This would allow to specify that ONLY such Objects can be held in the hands, and you can cast for them, if you want to (some people do not like run-time casting though).
Derived from the 'HandEquippableObject' is a 'WeaponBaseClass', that adds common data of weapons, like damage, attack speed and DamageType,...
After that either derive different weapon types like bows, swords, and so on, or add an enum to the WeaponBaseClass that the player can switch on, to distinguish different weapons (avoiding a deep hierarchy). It's a choice of preference.
Furthermore I typically only create functions for data-getters (since i wrap each layers data in a struct that'd make only 2 getters by now) and animation/presentation purpose (like playing a swing-sound for a sword, spawning flame-particles, Event to play a breaking animation on durability-loss, ...). The character needs a way to access the data, and the rest is handled by him (calculating actual dmg and attack speed, due to buffs, potions and stuff). One exception are bows, since those need to be drawn, and other 'aimed' weapons, but that's not relevant right now.